I'm new in React. I'm developing a screen but I have a issue, I don't know how insert the children in the parent if the state condition is equals, I'm using an array to print the parent and children but depends of the data the parent could have a children or not, for example if (parent.rework_name === children.rework_name) ? print the children : 'nothing in the parent'.
Please let me know if you have an idea how to solve this, many many thanks in advance.
This is the goal, my code works but the damn children is outside the parent :(
class Filling extends Component {
constructor() {
super();
this.state = {
fillingStations: [],
};
}
componentDidMount() {
getDataAPI('http://localhost:8080/api/parent')
.then((station) => {
getDataAPI('http://localhost:8080/api/children')
.then((data) => {
const stationArray = [];
station.map((item, index) => {
stationArray.push(
<ReworkStation key={index} title={index + 1} status='' />,
);
data.map((it, idx) => {
const f2Date = it.f2_time.substr(0, 10);
const f2Hour = it.f2_time.substr(11, 8);
const f2DateFormatted = `${f2Date.substr(8, 2)}/${f2Date.substr(5, 2)}/${f2Date.substr(0, 4)}`;
const color = selection_color(it.color_d);
return (
stationArray.push(item.rework_name === it.rework_name && <ReworkTitle key={idx} vin={it.vin} date={f2DateFormatted} ipsq={it.defects} hour={f2Hour} color={color} />)
);
});
});
console.log(stationArray);
this.setState({
fillingStations: stationArray,
});
});
});
}
render() {
return (
<div className='row'>
{ this.state.fillingStations }
</div>
);
}
}
I don't know how to insert the children inside the parent already render.
I already solved, first render all the parent divs and after replace the position array with array.splice
render() {
const array = [];
this.state.fillingStations.map((item, index) => (
array.push(<Parent key={index} title={index + 1} status='' />),
this.state.fillingChildren.map((it, ind) => {
if (item.name === it.name) {
parent.splice(index, 1,
<Parent {...this.props}}>
<Child {...this.props} />
</Parent >);
}
})
));
return (
<div className='row'>
{array}
</div>
);
}
}
Related
Hello [from react beginner].
Trying to pass child's input value to parent state.
So, App has an array:
export default class App extends React.Component {
state = {
data: [
{id: 1, name: 'john'},
{id: 2, name: 'doe'},
]
}
render() {
return (
<List data={this.state.data}/>
)
}
}
Then List takes prop.data as state.data and returns children in map:
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
this.parentChange = this.parentChange.bind(this);
}
renderList() {
const data = this.state.data;
let list = null;
if (data.length) {
list = data.map(function(item, index){
return (<Item key={item.id} data={item} onChange={(e, index) => this.parentChange(e, index)} />)
});
} else {
list = <p>nothing here</p>
}
return list;
}
parentChange(value, index) {
// pls give me anything
console.log('--- value: ', value);
console.log('--- index: ', index);
}
render() {
return (
<div>{this.renderList()}</div>
)
}
}
And Item child:
class Item extends React.Component {
render() {
const {id, name} = this.props.data;
return (
<div>
<input id={id} value={name} onChange={(e) => this.props.onChange(e, id)} />
</div>
)
}
}
But if I change any input's value there is an error as result
Cannot read property 'parentChange' of undefined
Thanks for any help (code, links etc)
You are declaring a function with function keyword:
if (data.length) {
list = data.map(function(item, index){
return (<Item key={item.id} data={item} onChange={(e, index) =>
this.parentChange(e, index)} />)
});
}
Declaring a function with the function keyword will create another context inside itself, so your this (context) will no longer be the class context.
The IDE might not warn you but when it runs, JS create another context inside your function result in an undefined error.
So it will need to change to:
if (data.length) {
list = data.map((item, index) => {
return (<Item key={item.id} data={item} onChange={(e, index) =>
this.parentChange(e, index)} />)
});
}
I have two components, a TrackSection(the Parent element) which has a button that creates a TrackItem(child) every time it is clicked. The child elements are built through a variable numTracks which increments every time the button is clicked. The add button works fine but i'm having issues deleting a TrackItem from the array. I tried referencing the track_items directly but it won't let me.
I'm very new to React and Frontend development. Any other tips would be appreciated!
TrackSection.js
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
numTracks: 0,
};
}
onAddTrack = () => {
this.setState({
numTracks: this.state.numTracks + 1,
});
};
onDeleteTrack = () =>{
//????
};
render() {
const track_items = [];
for (var i = 0; i < this.state.numTracks; i += 1) {
track_items.push(<TrackItem key={i} id={i} onDeleteTrack = {this.onDeleteTrack(i)}/>);
}
return (
<div>
<Button onClick={this.onAddTrack}>
+new track
</Button>
{track_items}
</div>
);
}
}
TrackItem.js
class TrackItem extends Component{
constructor(props){
super(props);
this.state = {
id: this.props.id,
name: '',
}
}
render(){
var onDeleteTrack = this.props.onDeleteTrack
return(
<Grid container direction="row">
<Grid item direction="column">
//Dummy
</Grid>
<button onClick={() => onDeleteTrack(this.props.id)}>Delete</button>
</Grid>
);
}}
Issue
You are using an array index as the React key, and the id. When you remove an element from the array you may remove it from the array, but since the items shift up to fill the "hole" now all the elements in the array have incorrect "id"/index values.
Solution
Don't use a mapped array index as a React key.
Example solution uses an incrementing id, as before, but also stores the array in state. This allows you to consistently increment the id key and retain a static id with each element.
class TrackItem extends Component {
constructor(props) {
super(props);
this.state = {
id: this.props.id,
name: ""
};
}
render() {
var onDeleteTrack = this.props.onDeleteTrack;
return (
<Grid container direction="row">
<Grid item direction="column">
//Dummy
</Grid>
<button onClick={() => onDeleteTrack(this.props.id)}>Delete {this.props.id}</button>
</Grid>
);
}
}
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
tracks: [],
id: 0,
};
}
onAddTrack = () => {
this.setState(prevState => ({
tracks: [...prevState.tracks, prevState.id],
id: prevState.id + 1,
}));
};
onDeleteTrack = (id) =>{
this.setState(prevState => ({
tracks: prevState.tracks.filter(el => el !== id)
}))
};
render() {
return (
<div>
<button onClick={this.onAddTrack}>
+new track
</button>
{this.state.tracks.map(track => (
<TrackItem key={track} id={track} onDeleteTrack = {this.onDeleteTrack}/>
))}
</div>
);
}
}
Be careful about doing too much logic in your render function, as your current solution would recreate all the TrackItem's every time you add a new item. So React can't do optimization magic.
Second remark, now you are just having a counter, so removing a element in the middle would probably not have the effect you are looking for. I assume the track items will be having some data to them. Like name, etc. So just store those values in the state and render each item.
Here is a sample solution, modify for your needs:
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
tracks: []
};
}
onAddTrack = () => {
// Probably not the best way to create a id
const randomId = Math.random().toString();
const newTrack = {
id: randomId,
name: "Some name" + randomId
};
const newTracks = [
// the tracks we allready have added
...this.state.tracks,
// add a new track to the end
newTrack
];
// Replace state
this.setState({
tracks: newTracks
});
};
onDeleteTrack = (id) => {
// Keeps all tracks that don't match 'id'
const tracksWithOutDeleted = this.state.tracks.filter(
(track) => track.id !== id
);
// Replace the tracks, so now its gone!
this.setState({
tracks: tracksWithOutDeleted
});
};
render() {
return (
<div>
<button onClick={this.onAddTrack}>+new track</button>
{
// Loop over tracks we have in state and render them
this.state.tracks.map((track) => {
return (
<TrackItem
id={track.id}
name={track.name}
onDeleteTrack={this.onDeleteTrack}
/>
);
})
}
</div>
);
}
}
And the TrackItem.js:
class TrackItem extends Component {
render() {
const { onDeleteTrack, id, name } = this.props;
return (
<>
<button onClick={() => onDeleteTrack(id)}>Delete {name}</button>
</>
);
}
}
I've created a React app for a school project that can add multiple types of input fields to a view by clicking a button (sort of like Wordpress Gutenberg).
Currently, I can add one of each type of item onto the view. However, if I click the button again, it erases the current text that was added. I'd like the ability to click the button to add as many fields as I'd like on click.
Also, the items are only added into the view in the order they were created meaning, even if I choose photo first and I click headline after, it (headline) will appear at the top of the list above the initial item.
I've had a look at these solutions (which were pretty good) but they didn't provide what I need.
Dynamically adding Input form field issue reactjs
and "update delete list elements using unique key": https://www.youtube.com/watch?v=tJYBMSuOX3s
which was closer to what I needed to do.
Apologies in advance for the length of the code,(there are two other related components for text input and an editform). I'm sure there is a much more simple way to do this. I haven't been able to find an npm package or solution to this specific problem online and am open to a simpler solution.
Edit.jsx
export default class Edit extends React.Component {
state = {
texts: {
hl: '',
shl: '',
txt: '',
photo: []
},
coms: {
hl: false,
shl: false,
txt: false,
photo: null
},
labels: {
// Replace with icons
hl: 'Headline',
shl: 'Sub',
txt: 'Text Area',
photo: 'Photo'
},
selectedItem: '',
}
componentDidMount() {
const saveData = localStorage.getItem('saveData') === 'true';
const user = saveData ? localStorage.getItem('user') : '';
this.setState({ user, saveData });
}
createPage = async () => {
await this.props.postPage(this.state.texts)
}
// add options
addOptions = (item) => {
const { coms } = this.state
coms[item] = !coms[item]
this.setState({ coms: coms })
}
// ADD TEXT
addTxt = () => {
this.setState({ texts: [...this.state.texts, ""] })
}
enableAllButtons = () => {
this.setState({ selectedItem: '' })
}
handleChange = (e, index) => {
this.state.texts[index] = e.target.value
//set the changed state.
this.setState({ texts: this.state.texts })
}
setDisable = (selectedItem) => {
this.setState({ selectedItem })
}
handleRemove = () => {
// this.state.texts.splice(index, 1)
this.setState({ texts: this.state.texts })
}
handleSubmit = (e) => {
console.log(this.state, 'all text')
}
handleChange = (e, item) => {
let { texts } = this.state
texts[item] = e.target.value
//set the changed state.
this.setState({ texts })
console.log(texts)
}
render() {
const { coms, labels, selectedItem, texts } = this.state
let buttons = Object.keys(coms)
let showItems = Object.keys(coms).filter(key => coms[key] === true)
return (
<div>
<InnerHeader />
{/* Make a route for edit here */}
<Route path='/edit/form' render={() => (
<EditForm
texts={texts}
coms={coms}
labels={labels}
addOptions={this.addOptions}
setDisable={this.setDisable}
selectedItem={selectedItem}
showItems={showItems}
handleChange={this.handleChange}
enableAllButtons={this.enableAllButtons}
/>
)} />
{/* Make route for preview */}
<Route path='/edit/preview' render={(props) => (
<Preview
{...props}
createPage={this.createPage}
/>
)}
/>
</div>
)
}
}
AddText.jsx:
export default class AddText extends Component {
state = {
}
// ADD TEXT
addTxt(item) {
const {
addOptions } = this.props
addOptions(item)
}
render() {
const { coms, labels } = this.props
const { selectedItem } = this.props
let buttons = Object.keys(coms)
console.log('here', selectedItem)
return (
<div>
<Card>
<Card.Body>
{
buttons.map((item, index) => <button
value={(selectedItem === "") ? false : (selectedItem === item) ? false : true} key={index} onClick={() => this.addTxt(item)}>
{labels[item]}
</button>
)
}
</Card.Body>
</Card>
</div>
)
}
}
EditForm.jsx
export default function EditForm(props) {
return (
<div>
<div className='some-page-wrapper-sm'>
<div className="dash-card-sm">
<button><Link to={{
pathname: '/edit/preview',
item: props.texts
}}>preview</Link></button>
<br />
<br />
<AddText
coms={props.coms}
labels={props.labels}
addOptions={props.addOptions}
setDisable={props.setDisable}
selectedItem={props.selectedItem}
/>
<div>
{
props.showItems.map((item, index) => {
return (
<InputFieldComponent
// setDisable={props.setDisable}
onChangeText={(e) => props.handleChange(e, item)}
enableAllButtons={props.enableAllButtons}
key={index}
item={item}
labels={props.labels}
texts={props.texts}
/>
)
})
}
</div>
</div>
</div>
</div>
)
}
InputFieldComponent.jsx
export default class InputFieldComponent extends React.Component {
setWrapperRef = (node) => {
this.wrapperRef = node;
}
render() {
const { labels, item, onChangeText, texts } = this.props
return (
<div>
<textarea
className="txt-box"
ref={this.setWrapperRef}
onChange={onChangeText}
placeholder={labels[item]}
value={texts[item]} />
</div>
)
}
}
I can't seem to pass this handler correctly. TabItem ends up with undefined for onClick.
SearchTabs
export default class SearchTabs extends Component {
constructor(props) {
super(props)
const breakpoints = {
[SITE_PLATFORM_WEB]: {
displayGrid: true,
autoFocus: true,
},
[SITE_PLATFORM_MOBILE]: {
displayGrid: false,
autoFocus: false,
},
};
this.state = {
breakpoints,
filters: null,
filter: null,
isDropdownOpen: false,
selectedFilter: null,
tabs: null,
};
this.tabChanged = this.tabChanged.bind(this);
this.closeDropdown = this.closeDropdown.bind(this);
}
... more code
createTabs(panels) {
if(!panels) return;
const tabs = panels.member.map((panel, idx) => {
const { selectedTab } = this.props;
const { id: panelId, headline } = panel;
const url = getHeaderLogo(panel, 50);
const item = url ? <img src={url} alt={headline} /> : headline;
const classname = classNames([
searchResultsTheme.tabItem,
(idx === selectedTab) ? searchResultsTheme.active : null,
]);
this.renderFilters(panel, idx, selectedTab);
return (
<TabItem
key={panelId}
classname={classname}
idx={idx}
content={item}
onClick={this.tabChanged(idx, headline)}
/>
);
});
return tabs;
}
tabChanged(idx, headline) {
const { selectedTab } = this.props;
const { selectedFilter } = this.state;
const selectedFilterIdx = _.get(selectedFilter, 'idx', null);
if (selectedTab !== idx) {
this.props.resetNextPage();
this.props.setTab(idx, selectedFilterIdx, headline);
this.closeDropdown();
}
}
render() {
// const { panels, selectedTab } = this.props;
// if (!panels || panels.length === 0) return null;
//
//
// const { tabs, selectedTab } = this.props;
return (
<div>
<ul>{this.state.tabs}</ul>
</div>
);
}
}
export const TabItem = ({ classname, content, onClick, key }) => (
<li key={key} className={`${classname} tab-item`} onClick={onClick} >{content}</li>
);
so in TabItem onClick={onClick} ends up with undefined for onClick.
More info
here's how this used to work, when this was a function in the parent Container:
// renderDefaultTabs() {
// const { panels, selectedTab } = this.props;
//
// if (!panels || panels.length === 0) return;
//
// let filter = null;
//
// const tabs = panels.member.map((panel, idx) => {
// const { id: panelId, headline } = panel;
// const url = getHeaderLogo(panel, 50);
// const item = url ?
// <img src={url} alt={headline} /> : headline;
// const classname = classNames([
// searchResultsTheme.tabItem,
// (idx === selectedTab) ? searchResultsTheme.active : null,
// ]);
//
// filter = (idx === selectedTab) ? this.renderFilters(panel) : filter;
//
// return (
// <li
// key={panelId}
// className={classname}
// onClick={() => {
// this.tabChanged(idx, headline);
// }}
// >
// {item}
// </li>
// );
// });
So I extracted that out to that SearchTabs including moving the tabChange d method to my new SearchTabs component. And now in the container the above now does this:
renderDefaultTabs() {
const {
onFilterClick,
panels,
resetNextPage,
selectedTab,
selectedFilter,
isDropdownOpen,
} = this.props;
return (<SearchTabs
panels={panels}
...
/>);
}
Note: renderDefaultTabs() is sent as a prop to in the render() of the container and the Search calls it back thus rendering it in the Search's render():
Container
render() {
return (
<Search
request={{
headers: searchHeaders,
route: searchRoute,
}}
renderTabs={this.renderDefaultTabs}
renderSearchResults={this.renderSearchResults}
handleInputChange={({ input }) => {
this.setState({ searchInput: input });
}}
renderAltResults={true}
/>
);
}
Search is a shared component our apps use.
Update
So I mentioned that the Container's render() passes the renderDefaultTabs function as a prop to <Search />. Inside <Search /> it ultimately does this: render() { <div>{renderTabs({searchResults})}</div>} which calls the container's renderDefaultTabs function which as you can see above, ultimately renders
So it is passing it as a function. It's just strange when I click a TabItem, it doesn't hit my tabChanged function whatsoever
Update
Christ, it's hitting my tabChanged. Errr..I think I'm good. Thanks all!
onClick={this.tabChanged(idx, headline)}
This is not a proper way to pass a function to child component's props. Do it like (though it is not recommended)
onClick={() => this.tabChanged(idx, headline)}
UPDATE
I want to add more explanation. By onClick={this.tabChanged(idx, headline)}, you are executing tabChanged and pass its returned value to onClick.
With your previous implementation: onClick={() => { this.tabChanged(idx, headline); }}, now onClick will be a function similar to:
onClick = {(function() {
this.tabChanged(idx, headline);
})}
So it works with your previous implementation.
With your new implementation, onClick={() => this.tabChanged(idx, headline)} should work
I'm trying to delete elements from a list on react but the way I've found to implement this requires a key to be assigned to reach element. I'm using the index from the list, but when I remove an item from the middle, it is always the last the one who disappears. How can I solve this?
This is how I'm removing the element (I've already logged the result and it is ok):
handle_delete_elem(index){
this.state.tasks.splice(index,1);
this.setState({
tasks: this.state.tasks
});
}
And this is how I'm looping through the list:
get_task_list(){
let task_list = [];
if(this.state && this.state.tasks){
for (var i = 0; i < this.state.tasks.length; i++) {
task_list.push((
<TaskItem
key={i}
index={i}
handle_delete={this.handle_delete_elem}
value={this.state.tasks[i].text}>
</TaskItem>
));
}
}
return task_list;
}
I solved this problem by used spread operators.
I've refactored your code a bit:
class List extends React.Component{
constructor(){
super();
this.state = {
arr: [1,2,3,4,5]
}
this.get_task_list = this.get_task_list.bind(this);
this.handle_delete_elem = this.handle_delete_elem.bind(this);
}
handle_delete_elem(index){
let newArray = [
...this.state.arr.slice(0, index),
...this.state.arr.slice(index+1, this.state.arr.length)
];
this.setState({
arr: newArray
});
}
get_task_list(){
if(this.state && this.state.arr){
return this.state.arr.map( (arrItem, index) => (
<ListItem
key={index}
arrItem={arrItem}
id={index}
onClick={this.handle_delete_elem}
/>
));
}
}
render(){
return(
<ul>{this.get_task_list()}</ul>
)
}
}
const ListItem = (props) => (
<li onClick={() => props.onClick(props.id)}>{props.arrItem}</li>
);
Check this in the fiddle: https://jsfiddle.net/69z2wepo/83396/