I want to update question name on onChange event. But problem is every question changes if I change only one ques.How to fix it ?
class QuestionCreate extends React.Component {
constructor(props){
super(props);
this.state = {
datas: [],
default_question: {
isNew: true,
question: {
name: 'Click to write the question text',
answer_options: [
{name: 'click to write choice 1', 'choice': true},
{name: 'click to write choice 1', 'choice': true},
{name: 'click to write choice 1', 'choice': true},
]
}
}
}
}
onChangeQuestion(qustions, index, event){
let all_data = this.state.datas;
let currentQuestion = all_data[index].question
all_data[index]['question']['name'] = event.target.value;
console.log(all_data[index]['question']['name'])
this.setState({datas: all_data})
}
displayRow(){
let rowData = this.state.datas.map( (data, index) =>{
console.log("this is data:", data, index);
return(
<div className="well" key={ index }>
<h3>Q. <input type="text" onChange={ this.onChangeQuestion.bind(this, data, index) } value={ data.question.name } /></h3>
<ul>
<li>option</li>
</ul>
</div>
)
})
return rowData;
}
You are mutating your state directly.
let all_data = this.state.datas;
let currentQuestion = all_data[index].question
all_data[index]['question']['name'] = event.target.value;
Use:
let all_data = JSON.parse(JSON.stringify(this.state.datas));
let currentQuestion = all_data[index].question;
all_data[index]['question']['name'] = event.target.value;
this.setState({datas: all_data});
These questions may also be helpful.
Deep merge of complex state in React
What is the most efficient way to deep clone an object in JavaScript?
it is working after editing onChangeQuestion function.
onChangeQuestion(qustions, index, event){
let all_data = JSON.parse(JSON.stringify(this.state.datas));
let currentQuestion = all_data[index].question
all_data[index]['question']['name'] = event.target.value;
this.setState({datas: all_data});
}
Related
I need to provide a sub component with an array of numbers from state - the array is defined by a provided total number (this.props.totalMarkerNumbers) prop (in string format) - i'm going astray with the syntax construct and wonder if anyone can please point me in the correct direction please?
Heres my code to create the array:
const ArrayCont = () => {
let i = 0;
let totalM = parseInt(this.props.totalMarkerNumbers, 10);
for (i = 0; i < totalM.length; i++) {
let no="''" + i +"''";
return(
{
label:no,
value:no
}
)
}
}
const Markers = () => {
let arrayCont =
return (
[{this.ArrayCont}]
)
}
This would be stored in state as:
class A_Log extends React.Component {
constructor(props) {
super(props);
this.state = {
markerArray:Markers,
};
}
basically if this.props.totalMarkerNumbersprovided '4' i'd want the array to be:
this.state.markerArray = [
{
label: '1',
value: '1',
},
{
label: '2',
value: '2',
},
{
label: '3',
value: '3',
},
{
label: '4',
value: '4',
},
];
theres probably a lot easier way to achieve this - any advice very welcome! Cheers.
I see 2 main issue in your code:
The first is this line:
let no="''" + i +"''";
I suppose you're trying to convert your int into a string, but it's not the correct way, you should instead use the toString function
The second is the return of your ArrayCont function.
With the provided code, you'll return an object containing one label and one value.
If you want to return an array, you have to create one and fill it each time you go through your loop
Here is a basic example:
function ArrayCont() {
let arraySize = parseInt(this.props.totalMarkerNumbers, 10);
const result = [];
for (var i = 1; i <= arraySize; i++) {
result.push({
label: i.toString(),
value: i.toString(),
});
}
return result;
}
Are you open to using a functional react component? Something like this does the trick:
import React, { useState } from 'react';
export default function App() {
const [ inputVal, setInputVal ] = useState('0');
const [ myArray, setMyArray ] = useState([]);
const handleChange = (e) => {
let val = e.currentTarget.value // get input value
setInputVal(val); // set it to state
let newArray = []; // build a new array
for (let i = 0; i < val; i++) { // for each between 0-val...
newArray.push({ // push obj to array
label: i + 1,
value: i + 1,
})
};
setMyArray(newArray); // set the array to state
}
return (
<div className="App">
<input
value={inputVal}
onChange={handleChange}
/>
<p>
{JSON.stringify(myArray)}
</p>
</div>
);
}
I reread your question and see if that you are interested in knowing how to pass props. So I've reworked my above example to show how you might ingest a string from an input, and use that to build (and show) your array.
import React, { useEffect, useState, useCallback } from "react";
/**
* EgChildComponent -- Function
* An example of how to pass state to a child component
* #param {number} inputVal The value of the input.
* #return {JSX.Element} A list of styled paragraph with your content
*/
const EgChildComponent = ({ inputVal }) => {
const [myArray, setMyArray] = useState([]);
const updateArray = useCallback((val) => {
let newArray = []; // build a new array
for (let i = 0; i < val; i++) {
// for each between 0-val...
newArray.push({
// push obj to array
label: i + 1,
value: i + 1
});
}
setMyArray(newArray); // set the array to state
}, [setMyArray]);
useEffect(() => {
if (myArray.length.toString() !== inputVal) {
updateArray(inputVal)
}
}, [inputVal, myArray.length, updateArray]);
const aChild = (childItem) => {
return (
<p key={`child-${JSON.stringify(childItem)}`}>
<b>Label: {childItem.label}</b>, value: {childItem.value}
</p>
);
};
return <>{myArray.map((item) => aChild(item))}</>;
};
/**
* Parent component where the value is set, and passed
* to the child via props, which then dynamically updates
* the array as desired.
*/
export default function App() {
const [inputVal, setInputVal] = useState("");
const handleChange = (e) => {
let val = e.currentTarget.value; // get input value
setInputVal(val); // set it to state
};
return (
<div className="App">
<input value={inputVal} onChange={handleChange} />
{inputVal !== '' && (
<EgChildComponent inputVal={inputVal} />
)}
</div>
);
}
Working CodeSandbox: https://codesandbox.io/s/stack-66696633-updatedarray-vm3rm
Not sure if I understand you right, but I guess this?
const getArr = (x) => Array.from(Array(+x),(_,i)=>({label:String(i+1),value:String(i+1)}))
console.log('Total markers 3:');
console.log(getArr('3'));
console.log('Total markers 5:');
console.log(getArr('5'));
console.log('Total markers 10:');
console.log(getArr('10'));
My code is basically a form with a text input and a submit button. Each time the user input data, my code adds it to an array and shows it under the form.
It is working fine; however, when I add duplicate values, it still adds it to the list. I want my code to count these duplicates and show them next to each input.
For example, if I input two "Hello" and one "Hi" I want my result to be like this:
2 Hello
1 Hi
Here is my code
import React from 'react';
import ShoppingItem from './ShoppingItem';
class ShoppingList extends React.Component {
constructor (props){
super(props);
this.state ={
shoppingCart: [],
newItem :'',
counter: 0 };
}
handleChange =(e) =>
{
this.setState ({newItem: e.target.value });
}
handleSubmit = (e) =>
{
e.preventDefault();
let newList;
let myItem ={
name: this.state.newItem,
id:Date.now()
}
if(!this.state.shoppingCart.includes(myItem.name))
{
newList = this.state.shoppingCart.concat(myItem);
}
if (this.state.newItem !=='')
{
this.setState(
{
shoppingCart: newList
}
);
}
this.state.newItem ="" ;
}
the rest of my code is like this:
render(){
return(
<div className = "App">
<form onSubmit = {this.handleSubmit}>
<h6>Add New Item</h6>
<input type = "text" value = {this.state.newItem} onChange ={this.handleChange}/>
<button type = "submit">Add to Shopping list</button>
</form>
<ul>
{this.state.shoppingCart.map(item =>(
<ShoppingItem item={item} key={item.id} />
)
)}
</ul>
</div>
);
}
}
export default ShoppingList;
Issues
this.state.shoppingCart is an array of objects, so this.state.shoppingCart.includes(myItem.name) will always return false as it won't find a value that is a string.
this.state.newItem = ""; is a state mutation
Solution
Check the newItem state first, if empty then return early
Search this.state.shoppingCart for the index of the first matching item by name property
If found then you want to map the cart to a new array and then also copy the item into a new object reference and update the quantity.
If not found then copy the array and append a new object to the end with an initial quantity 1 property.
Update the shopping cart and newItem state.
Code
handleSubmit = (e) => {
e.preventDefault();
if (!this.state.newItem) return;
let newList;
const itemIndex = this.state.shoppingCart.findIndex(
(item) => item.name === this.state.newItem
);
if (itemIndex !== -1) {
newList = this.state.shoppingCart.map((item, index) =>
index === itemIndex
? {
...item,
quantity: item.quantity + 1
}
: item
);
} else {
newList = [
...this.state.shoppingCart,
{
name: this.state.newItem,
id: Date.now(),
quantity: 1
}
];
}
this.setState({
shoppingCart: newList,
newItem: ""
});
};
Note: Remember to use item.name and item.quantity in your ShoppingItem component.
Replace your "handleSubmit" with below one and check
handleSubmit = (e) => {
e.preventDefault();
const { shoppingCart, newItem } = this.state;
const isInCart = shoppingCart.some(({ itemName }) => itemName === newItem);
let updatedCart = [];
let numberOfSameItem = 1;
if (!isInCart && newItem) {
updatedCart = [
...shoppingCart,
{
name: `${numberOfSameItem} ${newItem}`,
id: Date.now(),
itemName: newItem,
counter: numberOfSameItem
}
];
} else if (isInCart && newItem) {
updatedCart = shoppingCart.map((item) => {
const { itemName, counter } = item;
if (itemName === newItem) {
numberOfSameItem = counter + 1;
return {
...item,
name: `${numberOfSameItem} ${itemName}`,
itemName,
counter: numberOfSameItem
};
}
return item;
});
}
this.setState({
shoppingCart: updatedCart,
newItem: ""
});
};
I want to filter the data from my multiple states at one time. But I am getting the data of only second state.
I have two states and both states are getting seprate data from seprate apis. Now I want to filter the data from it. thank youI don't know what i m doing wrong so pls help me and look at my code.
searchFeatured = value => {
const filterFeatured = (
this.state.latestuploads || this.state.featuredspeakers
).filter(item => {
let featureLowercase = (item.name + " " + item.title).toLowerCase();
let searchTermLowercase = value.toLowerCase();
return featureLowercase.indexOf(searchTermLowercase) > -1;
});
this.setState({
featuredspeakers: filterFeatured,
latestuploads: filterFeatured
});
};
class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
featuredspeakers: [],
latestuploads: [],
};
}
componentDidMount() {
axios
.all([
axios.get(
'https://staging.islamicmedia.com.au/wp-json/islamic-media/v1/featured/speakers',
),
axios.get(
'https://staging.islamicmedia.com.au/wp-json/islamic-media/v1/featured/latest-uploads',
),
])
.then(responseArr => {
//this will be executed only when all requests are complete
this.setState({
featuredspeakers: responseArr[0].data,
latestuploads: responseArr[1].data,
loading: !this.state.loading,
});
});
}
Using the || (OR) statement will take the first value if not null/false or the second. What you should do is combine the arrays
You should try something like
[...this.state.latestuploads, ... this.state.featuredspeakers].filter(item=>{});
Ahmed, I couldn't get your code to work at all - searchFeatured is not called anywhere. But I have some thoughts, which I hope will help.
I see that you're setting featuredspeakers and latestuploads in componentDidMount. Those are large arrays with lots of data.
But then, in searchFeatured, you are completely overwriting the data that you downloaded and replacing it with search/filter results. Do you really intend to do that?
Also, as other people mentioned, your use of the || operator is just returning the first array, this.state.latestuploads, so only that array is filtered.
One suggestion that might help is to set up a very simple demo class which only does the filtering that you want. Don't use axios at all. Instead, set up the initial state with some mocked data - an array of just a few elements. Use that to fix the filter and search functionality the way that you want. Here's some demo code:
import React from 'react';
import { Button, View, Text } from 'react-native';
class App extends React.Component {
constructor(props) {
super(props);
this.searchFeatured = this.searchFeatured.bind(this);
this.customSearch = this.customSearch.bind(this);
this.state = {
loading: false,
featuredspeakers: [],
latestuploads: [],
};
}
searchFeatured = value => {
// overwrite featuredspeakers and latestuploads! Downloaded data is lost
this.setState({
featuredspeakers: this.customSearch(this.state.featuredspeakers, value),
latestuploads: this.customSearch(this.state.latestuploads, value),
});
};
customSearch = (items, value) => {
let searchTermLowercase = value.toLowerCase();
let result = items.filter(item => {
let featureLowercase = (item.name + " " + item.title).toLowerCase();
return featureLowercase.indexOf(searchTermLowercase) > -1;
});
return result;
}
handlePress(obj) {
let name = obj.name;
this.searchFeatured(name);
}
handleReset() {
this.setState({
featuredspeakers: [{ name: 'Buffy', title: 'Slayer' }, { name: 'Spike', title: 'Vampire' }, { name: 'Angel', title: 'Vampire' }],
latestuploads: [{ name: 'Sarah Michelle Gellar', 'title': 'Actress' }, { name: 'David Boreanaz', title: 'Actor' }],
loading: !this.state.loading,
});
}
componentDidMount() {
this.handleReset();
}
getList(arr) {
let output = [];
if (arr) {
arr.forEach((el, i) => {
output.push(<Text>{el.name}</Text>);
});
}
return output;
}
render() {
let slayerList = this.getList(this.state.featuredspeakers);
let actorList = this.getList(this.state.latestuploads);
return (
<View>
<Button title="Search results for Slayer"
onPress={this.handlePress.bind(this, {name: 'Slayer'})}></Button>
<Button title="Search results for Actor"
onPress={this.handlePress.bind(this, {name: 'Actor'})}></Button>
<Button title="Reset"
onPress={this.handleReset.bind(this)}></Button>
<Text>Found Slayers?</Text>
{slayerList}
<Text>Found Actors?</Text>
{actorList}
</View>
);
}
};
export default App;
You should apply your filter on the lists separately then. Sample code below =>
const searchFeatured = value => {
this.setState({
featuredspeakers: customSearch(this.state.featuredspeakers, value),
latestuploads: customSearch(this.state.latestuploads, value)
});
};
const customSearch = (items, value) => {
return items.filter(item => {
let featureLowercase = (item.name + " " + item.title).toLowerCase();
let searchTermLowercase = value.toLowerCase();
return featureLowercase.indexOf(searchTermLowercase) > -1;
});
}
I'm trying to set up so that when I type a name in the input of the Person component the state in the App component is updated and in turn updates the value of a prop in Person, however it appears the state change is happening, but the prop isn't updated, can anyone help me figure out what is wrong?
App
const App = () => {
const [persons, setPersons] = useState([
{id: "key1", name: "Daniel", age: "28"},
{id: "key2", name: "John", age: "30"},
{id: "key3", name: "Doe", age: "60"}
]);
const nameChangedHandler = (id, event) => {
console.log(event.target.value);
console.log(id);
const personIndex = persons.findIndex(p => {
return p.id === id;
});
const person = {
...persons[personIndex]
};
person.name = event.target.value;
const pers = [...persons];
persons[personIndex] = person;
setPersons(pers);
console.log(persons);
};
let people = persons.map((person, index) => {
return (
<Person
name={person.name}
key={person.id}
age={person.age}
changed={nameChangedHandler.bind(this, person.id)}
/>
);
});
return <div className="App">{people}</div>;
};
Person
const person = props => (
<div className={style.Person}>
<p onClick={props.click}>
I'm {props.name}, and I am {props.age}!
</p>
<input type="text" onChange={props.changed} value={props.name} />
</div>
);
You are assigning to the wrong variable, try the following:
const pers = [...persons];
pers[personIndex] = person;
And it should work as expected. Since you were updating your state object persons instead of the object you cloned pers, which you used to set the state, your console log was showing the expected output but your state wasn't being updated properly.
Check this working stackblitz
To be honest, I would use a simple map function to change the name of the particular person.
Inside nameChangedHandler function:
const updatedPersons = persons
.map((person) => person.id === id ? {...person, name: event.target.value} : person);
and then update the local state
setPersons(updatedPersons);
It should work as expected.
I've got a nested object that somehow retains a linkage to a profile object. Every time I call myMethod and makes changes to userObj, these changes are reflected in all elements of the nested object. For example, allProfiles['a'] and allProfiles['b'] have the same values for allProfiles[][].userObj properties. This only happens with the userObj data, everything is works as expected. The following snippet duplicates the issue.
import React from 'react';
import { ReactDOM, render } from 'react-dom';
export const userProfile = {
address1: { label: "Local", data: null },
address2: { label: "World", data: null },
};
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
profiles: {}
};
this.addProfile = this.addProfile.bind(this);
}
addProfile() {
const { counter, profiles } = this.state;
let new_user = Object.assign({}, userProfile);
new_user.address1.data = counter * 5;
const profile_id = 1;
const user_id = counter;
const new_profile = {};
const show = true;
new_profile[profile_id] = { show };
new_profile[profile_id] = new_user;
profiles[user_id] = new_profile;
this.setState({ counter: user_id + 1, profiles });
}
render() {
const profile_id = 1;
const ctr = this.state.counter;
return (
<div>
<div><button onClick={this.addProfile}>add profile</button></div>
<div>profile 0 data:
{ctr > 0 ? this.state.profiles[0][profile_id].address1.data : null}</div>
<div>profile {ctr} data:
{ctr > 0 ? this.state.profiles[ctr - 1][profile_id].address1.data : null}</div>
</div>
);
}
}
export default (Toggle);
render(<Toggle />, document.getElementById('root'));
Object.assign({}, userProfile) only creates a shallow copy of userProfile, meaning new_user.address1 and userProfile.address1 are still referencing the same object.
To properly clone the address1 and address2 objects, you need to do
const new_user = Object.entries(userProfile).reduce((acc, [key, value]) => {
acc[key] = { ...value }; // create a shallow copy of the nested object
return acc;
}, {});
This should fix your problem but there's a lot of weird things going on in your code, like assigning new_profile[profile_id] consecutively.