I have problem and I don't know how to fix it.
So i have component in which I've declared an array of objects.
I want to set its state separately but I don't want to declare multiple useStates.
I have an array of objects which look like this:
const [card, setCard] = useState({
name: "",
questions: [
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
],
});
and here's component:
const NewCard = () => {
const handleNameChange = (event) => {
setCard({ name: event.target.value, ...questions });
};
return (
<div className="newcard-container">
<div className="card-container">
<h3>Podaj nazwe fiszki</h3>
<input type="text" value={card.name} />
</div>
<div className="questions-container">
{card.questions.map((q) => {
return (
<div className="question">
<h4>Podaj pytanie </h4>
<input type="text" value={q.question} />
<h4>Podaj odpowiedź</h4>
<input type="text" value={q.answer} />
</div>
);
})}
<button>Dodaj pytanie</button>
</div>
</div>
);
};
I've tried to figure out how to change the setState to get that approach but I didn't made it. Any ideas how can I get that?
Again, not sure if this is what you needed so let me know.
import React, { useState, useCallback } from 'react';
export function App() {
const [card, setCard] = useState({
name: "",
questions: [
{
id: 'question-1',
question: "Question 1",
answer: "",
},
{
id: 'question-2',
question: "Question 2",
answer: "",
},
{
id: 'question-3',
question: "Question 3",
answer: "",
},
]
});
const handleCardNameChange = useCallback((ev) => {
setCard((c) => ({ ...c, name: ev.target.value }))
}, [setCard]);
const handleAnswerChange = useCallback((cardId, value) => {
const updatedQuestions = card.questions.map((c) => {
if (c.id !== cardId) {
return c;
}
return {
...c,
answer: value,
}
});
setCard({
...card,
questions: updatedQuestions,
})
}, [card, setCard]);
return (
<div>
<input placeholder="Card Title" value={card.name} onChange={handleCardNameChange} />
{card.questions.map((c) => (
<div key={c.id}>
<p>Q: {c.question}</p>
<input placeholder="Answer" value={c.answer} onChange={(ev) => handleAnswerChange(c.id, ev.target.value)} />
</div>
))}
</div>
);
}
This handles answer change per question and card title change separately. I wrote this in a some weird editor online so it might not be perfect but it should work.
it should be
setCard((card) => { ...card , name: event.target.value });
You have a few approaches to do this.
const [ card, setCard ] = useState( {
name: "",
questions: {
1: {
statement: "",
answer: "",
},
2: {
statement: "",
answer: "",
},
//...
}
} );
// To set an especifique answer or question, you can set the state like this:
setCard( prev => ( {
...prev,
questions: {
...prev.questions,
1: {
...prev.questions[ 1 ],
answer: "New answer"
}
}
} ) );
// To add a new question, you can set the state like this:
setCard( prev => ( {
...prev,
questions: {
...prev.questions,
[ Object.keys( prev.questions ).length + 1 ]: {
statement: "",
answer: "",
}
}
} ) );
// To remove a question, you can set the state like this:
setCard( prev => {
const questions = { ...prev.questions };
delete questions[ 1 ];
return {
...prev,
questions
};
} );
But if you wanna use with array, you can do like this:
// Solution with array
const [card, setCard] = useState({
name: "",
questions: [
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
//...
],
} );
// To set an especifique answer or question, you will need the index of the question, or null to set a new question.
const setCardQuestions = ( index, question, answer ) => {
setCard( ( prev ) => {
const questions = [...prev.questions];
if ( index === null ) {
questions.push( {
question,
answer,
} );
} else {
questions[ index ] = {
question,
answer,
};
}
return {
...prev,
questions,
};
});
};
// To remove a question, you will need the index of the question.
const removeCardQuestion = ( index ) => {
setCard( ( prev ) => {
const questions = [...prev.questions];
questions.splice( index, 1 );
return {
...prev,
questions,
};
});
}
Related
How to add a class name in every row without effect the rest of the rows
import React, { useState } from 'react';
import './testEfect.css';
const Test = () => {
const arrayTest = [
{
name: '11',
id: '11'
},
{
name: '22',
id: '22'
},
{
name: '33',
id: '33'
},
]
const [state, setState] = useState(false);
const handleClick = (event) => {
const newState = event;
setState(state ? false : true);
}
return (
<div className="App">
{arrayTest.map((x, index) => {
return (
<ul key={index} className={state ? 'deletEfect' : ''}>
<li id={x.id} >
{x.name}
<button onClick={(event) => handleClick(x.id)}>Delete</button>
</li>
</ul>
)
})}
</div>
)
}
The problem here is that when you say the state is false; it is assuming the state is false for the whole component. It doesn't update the row but the whole component. So, at first, you need to add a deleted property that will take a different value for each row.
So,
const arrayTest = [
{
name: "11",
id: "11",
deleted: false
},
{
name: "22",
id: "22",
deleted: false
},
{
name: "33",
id: "33",
deleted: false
}
];
const [state, setState] = useState(arrayTest); //initial state
Now, when you render, you don't need to use that arrayTest. But you need to use the state. We won't touch arrayTest ever again. So we use,
{state.map((x, index) => {
return (
<ul key={index} className={x.deleted ? "testEfect" : ""}>
<li id={x.id}>
{x.name}
<button onClick={(event) => handleClick(x.id)}>Delete</button>
</li>
</ul>
);
})}
Notice we use state.map. We also send x.id to handleClick function.
Why? Because we will use that id to change the deleted value of the object. So our handleClick becomes,
const handleClick = (id) => {
const newState = state.map((element) => {
if (element.id === id)
return Object.assign({}, element, {
deleted: element.deleted ? false : true
});
return element;
});
setState(newState);
};
This is just updating the state in an immutable way.
Here is the full codesandbox for your convenience.
I hope to be descriptive, Let's say I have a Files Object Array
JSONfiledata = [
{
lastModified:123444,
name: 'file1',
size: 0,
type: ""
},
{
lastModified:123445,
name: 'file2',
size: 0,
type: ""
},
{
lastModified:123446,
name: 'file3',
size: 0,
type: ""
}
]
And I have a this component that receives that data through props
import React, {useState} from 'react'
const component = ({files}) => {
const [inputValue, setInputValue] = useState('')
const eventHandler = (e) => setInputValue(e.target.value)
const addNewKey = files.map(fileObj => Object.defineProperty(fileObj, 'newKey', {
value: inputValue
}))
return (
{
files.map(fileData => (<div>
{fileData.name}
<input value={inputValue} onChange={setInputValue} />
</div>))
}
)
}
How can I mutate the current files object and add a 'newKey' on each one depending on the inputValue, but independently from each other.
I mean, at position 0 let's say I write on the input "this is the file number one"
at position 1 "this is the file number two" and so on ....
At the end, the expected output will be
[
{
lastModified:123444,
name: 'file1',
size: 0,
type: "",
newKey: "this is the file number one"
},
{
lastModified:123445,
name: 'file2',
size: 0,
type: "",
newKey: "this is the file number two"
},
{
lastModified:123446,
name: 'file3',
size: 0,
type: "" ,
newKey: "this is the file number three"
}
]
I build a solution:
Build another component to manage every file individualy.
Like this:
import React, { useState } from 'react';
import { Map } from './Map';
export const MapList = ({ files }) => {
const [filesState, setFilesState] = useState([...files]);
const handleChange = nObject => {
/**You can compare with a unique id, preferably */
setFilesState(filesState => filesState.map(file => (file.name === nObject.name ? nObject : file)));
};
return (
<div>
{filesState.map(file => (
// If you have an ID you can send in this plance, to be more simple find the object in the handle function
<Map handleChange={handleChange} file={file} />
))}
<h2>Files Change</h2>
{filesState.map(file => (
<div>
<p>
{file.name} {file.newKey && file.newKey}
</p>
</div>
))}
</div>
);
};
In this wrapper component, you will update the entry array, with the handleChange function.
After you can build a component to manage your new key, for example:
import React, { useState } from 'react';
export const Map = ({ file, handleChange }) => {
const [input, setInput] = useState('');
const handleChangeKey = e => {
const { name, value } = e.target;
const nFile = { ...file, [name]: value };
setInput(value);
handleChange(nFile);
};
return (
<div>
<div>
<label htmlFor={file.name}>
<small>Input for: {file.name}</small>{' '}
</label>
<input id={file.name} name='newKey' value={input} onChange={handleChangeKey} type='text' />
</div>
</div>
);
};
It works for me, i think is a solution maybe not the best, but is a simple solutions.
const JSONfiledata = [
{
lastModified:123444,
name: 'file1',
size: 0,
type: ""
},
{
lastModified:123445,
name: 'file2',
size: 0,
type: ""
},
{
lastModified:123446,
name: 'file3',
size: 0,
type: ""
}
];
const fileNameToUpdate = 'file2';
const newKey = "file2Key";
const newArray = JSONfiledata.map((item) => {
if (item.name === fileNameToUpdate) {
return {...item, newKey: newKey };
} else {
return item;
}
});
console.log(`newArray==`, newArray);
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have a nested object:
{
id: "id",
name: "Name",
type: "SC",
allgemein: {
charname: "Name",
spieler: "Jon",
},
eigenschaften: {
lebenspunkte: "30",
},
talente: {},
zauber: {},
}
With my form I'm trying to create a new object. Most of it works, but in the function handleSubmit, I'm trying to set the nested spieler to "TEST".
import React from "react";
import { TextField, Button } from "#material-ui/core/";
export default class extends React.Component {
state = this.getInitState();
getInitState() {
const { charakterID } = this.props;
return charakterID
? charakterID
: {
name: "",
allgemein: {
charname: "",
spieler: "",
},
eigenschaften: {},
talente: {},
zauber: {},
};
}
componentWillReceiveProps({ charakterID }) {
this.setState({
...charakterID,
});
}
handleChange = (n) => ({ target: { value } }) => {
this.setState({
[n]: value,
});
};
handleChangeAllg = (n) => ({ target: { value } }) => {
this.setState((prevState) => ({
...prevState,
allgemein: {
...prevState.allgemein,
charname: value,
},
}));
};
handleSubmit = () => {
this.props.onSubmit({
id: this.state.name.toLocaleLowerCase().replace(/ /g, "-"),
type: "SC",
allgemein: {spieler: "TEST"},
...this.state,
});
this.setState(this.getInitState());
};
render() {
const {
name,
allgemein: { charname },
} = this.state,
{ charakterID } = this.props;
console.log("fired");
console.log(this.props.onCreate);
return (
<form>
<TextField
label="name"
value={name}
onChange={this.handleChange("name")}
margin="dense"
fullWidth
/>
<br />
<TextField
label="charname"
value={charname}
onChange={this.handleChangeAllg("charname")}
margin="dense"
fullWidth
/>
<br />
<Button color="primary" variant="contained" onClick={this.handleSubmit}>
{charakterID ? "Edit" : "Neu"}
</Button>
</form>
);
}
}
It wont work and I don't know why. Can you help me?
Try if this works
handleSubmit = () => {
this.props.onSubmit({
...this.state, // Changed Position
id: this.state.name.toLocaleLowerCase().replace(/ /g, "-"),
type: "SC",
allgemein: {...this.state.allgemein,spieler: "TEST"},
});
this.setState(this.getInitState());
};
I'm trying to filter data based on a simple user search input.
I'm not sure if its the way i'm filtering the data, but whenever I input something in the text box, the data disappears. I can see in dev tools that the state of the query is being stored.
Here's my code in my context file. I'm planning on adding additional filters once the search function is fixed, thus the reason for the more complicated code.
import * as React from "react";
const DefaultState = {
cardListings: [],
filter: {}
};
const CardListingsContext = React.createContext(DefaultState);
export const CardListingsConsumer = CardListingsContext.Consumer;
export class CardListingsProvider extends React.Component {
static applyFilter(cards, filter) {
const { query } = filter;
let result = cards;
if (query) {
const search = query.toLowerCase();
result = result.filter(item => item.title.indexOf(search) !== -1);
}
return result;
}
state = DefaultState;
componentDidMount() {
fetch("http://localhost:9000/mwBase")
.then(res => res.json())
.then(res => {
this.setState({ cardListings: res });
});
}
updateFilter = filter => {
this.setState({
filter
});
};
render() {
const { children } = this.props;
const { cardListings, filter } = this.state;
const filteredListings = CardListingsProvider.applyFilter(
cardListings,
filter
);
return (
<CardListingsContext.Provider
value={{
allListings: cardListings,
cardListings: filteredListings,
updateFilter: this.updateFilter
}}
>
{children}
</CardListingsContext.Provider>
);
}
}
Here's my input form
<form
className={formClasses}
noValidate
onChange={() =>
setTimeout(() => this.props.updateFilter(this.state), 0)
}
>
<p className="mb-1">Refine your results</p>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Search for a card..."
name="query"
value={this.state.query}
onChange={event => this.setState({ query: event.target.value })}
/>
</div>
and where the Filter is being applied on my home page:
<CardListingsProvider>
<CardListingsConsumer>
{function(value) {
const { cardListings, updateFilter } = value;
return (
<>
<Filter updateFilter={updateFilter} />
<div className="columns">
{cardListings.map(item => (
<Card key={item.itemId} card={item} />
))}
</div>
</>
);
}}
</CardListingsConsumer>
</CardListingsProvider>
</div>
Here's example of my dataset:
[
{
itemId: [
"120901386991"
],
title: [
"1952 Topps Mickey Mantle Chase Card Box 18 packs 5 1950s or 1960's cards per box"
],
globalId: [
"EBAY-US"
],
subtitle: [
"3 BX LOT. 1 VINTAGE PK PER 25 BOXES* LOOK 4 1952 MANTLE"
],
primaryCategory: [
{
categoryId: [
"213"
],
categoryName: [
"Baseball Cards"
]
}
],
secondaryCategory: [
{
categoryId: [
"156521"
],
categoryName: [
"Vintage Non-Sport Cards"
]
}
],
galleryURL: [
"https://thumbs4.ebaystatic.com/m/m1mtMB65mAApWQ2EhJy4qWA/140.jpg"
],
viewItemURL: [
"https://rover.ebay.com/rover/1/711-53200-19255-0/1?ff3=2&toolid=10044&campid=5338164673&customid=watchbask&lgeo=1&vectorid=229466&item=120901386991"
],
paymentMethod: [
"PayPal"
],
autoPay: [
"true"
],
location: [
"USA"
],
country: [
"US"
],
shippingInfo: [
{
shippingServiceCost: [
{
#currencyId: "USD",
__value__: "0.0"
}
],
shippingType: [
"Free"
],
shipToLocations: [
"Worldwide"
],
expeditedShipping: [
"false"
],
oneDayShippingAvailable: [
"false"
],
handlingTime: [
"1"
]
}
],
sellingStatus: [
{
currentPrice: [
{
#currencyId: "USD",
__value__: "118.0"
}
],
convertedCurrentPrice: [
{
#currencyId: "USD",
__value__: "118.0"
}
],
sellingState: [
"Active"
],
timeLeft: [
"P10DT14H59M31S"
]
}
],
listingInfo: [
{
bestOfferEnabled: [
"false"
],
buyItNowAvailable: [
"false"
],
startTime: [
"2012-04-23T16:52:17.000Z"
],
endTime: [
"2019-10-23T16:52:17.000Z"
],
listingType: [
"FixedPrice"
],
gift: [
"false"
],
watchCount: [
"443"
]
}
],
returnsAccepted: [
"false"
],
condition: [
{
conditionId: [
"1000"
],
conditionDisplayName: [
"Brand New"
]
}
],
isMultiVariationListing: [
"false"
],
pictureURLLarge: [
"https://i.ebayimg.com/00/s/NTAwWDMxNA==/z/sT8AAOSw62VZv9qQ/$_1.JPG"
],
topRatedListing: [
"false"
]
},
In your case title is an array of string. If it is supposed to contain only one element. You can change your filter function from
result.filter(item => item.title.indexOf(search) !== -1);
to
result.filter(item => item.title[0].indexOf(search) !== -1);
If the title array contains multiple items, You could do use Array.some
result.filter(item =>
item.title.some(eachTitle => {
return eachTitle.indexOf(search) !== -1
})
)
And if you need case insensitive filter, you might need to change the filter function on that aspect too.
const search = query.toLowerCase();
result.filter(item => item.title[0].toLowerCase().indexOf(search) !== -1);
Looks like the code snippet you have posted might not be complete. I see some unbalanced parentheses for applyFilter Function in your Provider component.
static applyFilter(cards, filter) {
const { query } = filter;
let result = cards;
if (query) {
const search = query.toLowerCase();
result = result.filter(item => item.title.indexOf(search) !== -1);
}
state = DefaultState;
Also I'm wondering why would you need a setTimeout to call setState function in Filter component. The below
onChange={() =>
setTimeout(() => this.props.updateFilter(this.state), 0)
}
You can get rid of that as well.
I have made some edits to complete applyFilter function to return the filtered data. Please have a look at the below code and Run Code Snippet to see the code in action. Hope this helps!
// Provider Class
const DefaultState = {
cardListings: [],
filter: {}
};
const CardListingsContext = React.createContext(DefaultState);
const CardListingsConsumer = CardListingsContext.Consumer;
class CardListingsProvider extends React.Component {
static applyFilter(cards, filter) {
const {
query
} = filter;
let result = cards;
if (query) {
const search = query.toLowerCase();
result = result.filter(item => item.title[0].toLowerCase().indexOf(search) !== -1);
}
return result;
}
state = DefaultState;
componentDidMount() {
Promise.resolve([
{
itemId: ['1'],
title: ['Apple']
},
{
itemId: ['2'],
title: ['Orange']
},
{
itemId: ['3'],
title: ['Peach']
}
]).then(res => {
this.setState({
cardListings: res
});
});
}
updateFilter = filter => {
this.setState({
filter
});
};
render() {
const {
children
} = this.props;
const {
cardListings,
filter
} = this.state;
const filteredListings = CardListingsProvider.applyFilter(
cardListings,
filter
);
return ( <
CardListingsContext.Provider value = {
{
allListings: cardListings,
cardListings: filteredListings,
updateFilter: this.updateFilter
}
} >
{
children
}
</CardListingsContext.Provider>
);
}
}
class Filter extends React.Component {
state = { query: "" };
render() {
return (
<form
noValidate
onChange={() =>
setTimeout(() => this.props.updateFilter(this.state), 0)
}
>
<p className="mb-1">Refine your results</p>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Search for a card..."
name="query"
value={this.state.query}
onChange={event => this.setState({ query: event.target.value })}
/>
</div>
</form>
);
}
}
class Home extends React.Component {
render() {
return (
<div>
<CardListingsProvider>
<CardListingsConsumer>
{function(value) {
const { cardListings, updateFilter } = value;
return (
<React.Fragment>
<Filter updateFilter={updateFilter} />
<div className="columns">
{cardListings.map(item => (
<div key={item.itemId}>{JSON.stringify(item)}</div>
))}
</div>
</React.Fragment>
);
}}
</CardListingsConsumer>
</CardListingsProvider>
</div>
);
}
}
ReactDOM.render( <Home /> , document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
My problem is when I'm deleting inputs that added dynamically it delete's wrong input. I reproduced my code in jsfiddle https://jsfiddle.net/armakarma/qwg3j2fa/24/ . Try to add five more inputs, type something in each input and try to delete second input. It will delete last one. Where I'm doing mistake?
addNewInputs() {
let newInputValues = {
datetime: "10.05.2019 14:00",
position_id: 1,
contact: "",
address_id: "",
new_address: "",
}
this.setState(prevState => ({
arrayOfAddresses: [...prevState.arrayOfAddresses, newInputValues],
}))
}
deleteInput(idx) {
let tempObj = this.state.arrayOfAddresses
tempObj.splice(idx, 1)
this.setState(prevState => ({
arrayOfAddresses: tempObj,
}))
}
onChooseAddress(e, idx) {
console.log(e.target.value)
}
render() {
return ( <
div > {
this.state.arrayOfAddresses.map((item, idx) => {
return (
<div key = {idx} >
<input name = "contact"
onChange = {(e) => this.onChooseAddress(e)}
/>
<button onClick = {() => this.deleteInput(idx)} > x < /button>
</div>
)
})
}
<button onClick = {() => this.addNewInputs()} > Add new input < /button>
/div>
)
}
}
The problem is with the chooseAddress method, you're not passing the index from the onChange callback, that's why the state is not updating, and also you have not added value prop to the input, that's why rendering was wrong, because of input's internal state
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.state = {
adresses:[
{
"id": 1,
"address": "address 1",
},
{
"id": 2,
"address": "address 2",
},
{
"id": 3,
"address": "address 3",
},
{
"id": 4,
"address": "address 4",
}
],
arrayOfAddresses: [
{
datetime: "10.05.2019 14:00",
position_id: 1,
contact: "",
address_id: "",
new_address: "",
},
],
}
}
addNewInputs() {
let newInputValues = {
datetime: "10.05.2019 14:00",
position_id: 1,
contact: "",
address_id: "",
new_address:"",
}
this.setState(prevState => ({
arrayOfAddresses: [...prevState.arrayOfAddresses, newInputValues],
}))
}
deleteInput(idx) {
this.setState(prevState => {
let tempObj = [...prevState.arrayOfAddresses]
tempObj.splice(idx, 1)
console.log(tempObj)
return {
arrayOfAddresses: tempObj,
}
})
}
onChooseAddress(e, idx) {
const {value} = e.target;
this.setState(state=>{
let tempObj = [...this.state.arrayOfAddresses]
tempObj[idx].new_address = value
return {
arrayOfAddresses: tempObj,
}
})
}
render() {
return (
<div>
{this.state.arrayOfAddresses.map((item,idx)=>
<div>
<input
name="contact"
value={item.new_address}
onChange={(e) => this.onChooseAddress(e, idx)}
/>
<button onClick={() => this.deleteInput(idx)}> x</button>
</div>
)}
<button onClick={() => this.addNewInputs()}> Add new input </button>
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
There are two things you need to change:
Set the value of <input>. The problem is that the arrayOfAddresses is set correctly, but correct values are not reflected in the input.
Add the corresponding idx value to the onChange of <input>
Here's the relevant code change:
<input name="contact" value={item.new_address} onChange={(e) => this.onChooseAddress(e, idx)}
/>
Here's the fiddle:
JSFiddle