I created an array of objects with properties of name and birth_date. I then create a Person component and map out the array using props. I'd like to make the name editable so I put the props into an input value. I am trying to use hooks to be able to update that objects name, however, what I currently have only returns an empty value and then edits all values in the table. Any way to specify to only edit that particular objects name and update the array?
NewTable.js
import React, { useState } from 'react';
import './list.css';
import TableHeader from './TableHeader';
import PersonItem from './PersonItem';
import { people } from './people.js';
function NewTable() {
const [itemDetails, editItemDetails] = useState(people.name)
function newPeople(people) {
return (
<PersonItem
name={itemDetails}
date={people.birth_date}
edit={e => editItemDetails(e.target.value)}
/>
);
}
return(
<div id="task-group">
<table className="task-list">
<TableHeader />
<tbody>
{people.map(newPeople)}
</tbody>
</table>
</div>
)
}
export default NewTable;
PersonItem.js
import React from 'react';
import './list.css';
function PersonItem(props) {
return(
<tr>
<td><input type="text" value={props.name} onChange={props.edit}/></td>
<td><input type="text" value={props.date}/></td>
</tr>
)
}
export default PersonItem;
people.js
const people = [
{
name: "John Smith",
birth_date: '01/01/1991',
},
{
name: "Dwayne Johnson",
birth_date: '03/05/1992',
},
]
export { people };
Here is one possible solution:
Have a unique id for every person:
const myPeople = [
{
id: "1",
name: "John Smith",
birth_date: '01/01/1991',
},
{
id: "2",
name: "Dwayne Johnson",
birth_date: '03/05/1992',
},
]
Then pass down this function to the PersonItem component so they can call up and change the state:
const [people, setPeople] = useState(myPeople)
// Pass this function as a prop to PersonItem
function changeNameOfPerson(id, newName) {
const peopleCopy = [...people];
for (let person in peopleCopy) {
if (person.id === id) {
person.name = newName;
}
}
setPeople(peopleCopy);
}
A good practice when using hooks is to name the function that changes the state with the prefix 'set'.
Example: [nameOfYourVar, setNameOfYourVar]
This improves the readability.
Like Mellet said, it's important to use an unique identifier on each object. you can use this id to change an specific object from your array. Source
Related
I have an object:
import React, {Component} from 'react';
import ProgressBar from "./ProgressBar";
class Languages extends Component {
state = {
languages: [
{id: 1, value: "XXX", xp: 1.5},
{id: 2, value: "CCC", xp: 1},
{id: 3, value: "AAA", xp: 2}
]
}
render() {
let {languages} = this.state;
const levels = ['Test', 'Bad', 'Sorry']
return (
<div className="languages">
<ProgressBar
programming={languages}
className="languagesDisplay"
levels={levels}
title="Languages"
/>
</div>
);
}
}
export default Languages;
import React from 'react';
const ProgressBar = (props) => {
return (
<div className={props.className}>
<h3>{props.title}</h3>
<div className="years">
<span>Experiences</span>
props.levels.map((level) => {
<span>level</span>
})
</div>
<span>level</span> return props.levels.map((level) =>level)
how can i display the const ['Test', 'Bad', 'Sorry'] from Languages.js in a <span> in a different React file?
Edit after seeing your response above: If the issue is just that the above code isn't working, here are a couple of things to check.
Inside of ProgressBar you've got a couple of errors in your JSX. First, you need curly braces around your JavaScript interpolation and secondly, you're not returning anything in your .map() function. If you were using parentheses it would be an implicit return, but with the curly braces you need a return statement. Try this instead:
import React from 'react';
const ProgressBar = (props) => {
return ( <div className={props.className}>
<h3> {props.title} </h3>
<div className ="years">
<span> Experiences </span>
{props.levels.map((level) => {
return (<span>{level}</span>)
})
} </div>
)};
My initial answer, which still may be helpful for understanding what's going on:
It's not entirely clear what you want to do, but here are a couple of things that might be helpful.
What's happening in your code above is that the levels variable, which is an array of strings, is being passed down from the parent component Languages into the child component ProgressBar via the props object.
When ProgressBar is called inside of Languages, it's properties (or props) are set (programming, className, levels, title).
The levels={levels} part means that the prop levels on ProgressBar is being set to the variable levels (the array of strings).
Inside of ProgressBar all of those properties are accessible in the props object that's passed as an argument. That's why you're able to access that array of strings with props.levels.map() which will map the array of strings however you tell it to (in this case by printing each individual item within a <span> tag).
So, with that understanding of what's happening here, here are a couple of things you could do to access the levels variable elsewhere in another file.
If levels is a constant that you want to access in multiple places, you could move it outside of the body of your Languages component and export it to use it in other places.
That could look like:
import React, {
Component
} from 'react';
import ProgressBar from "./ProgressBar";
export const levels = ['Test', 'Bad', 'Sorry']
class Languages extends Component {
state = {
languages: [{
id: 1,
value: "XXX",
xp: 1.5
},
{
id: 2,
value: "CCC",
xp: 1
},
{
id: 3,
value: "AAA",
xp: 2
}
]
}
render() {
let {
languages
} = this.state;
return ( <
div className = "languages" >
<
ProgressBar programming = {
languages
}
className = "languagesDisplay"
levels = {
levels
}
title = "Languages" /
>
<
/div>
);
}
}
export default Languages;
By exporting it from the top level, you could import it in another file exactly as it is.
import { levels } from '/insert-first-file-location-here'
Another option is to pass the levels variable into another component as a prop. This way if levels gets changed at the top level, those changes will drill down into subsequent components.
import React, {Component} from 'react';
import ProgressBar from "./ProgressBar";
class Languages extends Component {
state = {
languages: [
{id: 1, value: "XXX", xp: 1.5},
{id: 2, value: "CCC", xp: 1},
{id: 3, value: "AAA", xp: 2}
]
}
render() {
let {languages} = this.state;
const levels = ['Test', 'Bad', 'Sorry']
return (
<>
<div className="languages">
<ProgressBar
programming={languages}
className="languagesDisplay"
levels={levels}
title="Languages"
/>
</div>
<AnotherComponentThatUsesLevels
levels={levels} />
</>
);
}
}
export default Languages;
And then
import React from 'react'
export const AnotherComponentThatUsesLevels (props) => {
return (
<>
{/* do something with levels here, maybe map them like before*/}
{props.levels.map((level) => (<span>{level}</span>)}
</>
)
}
Does that help understand what's happening in the example and give you a couple of ways you could use that variable in another location?
You need to export that certain constant from your file like that:
import React, {
Component
} from 'react';
import ProgressBar from "./ProgressBar";
export const levels = ['Test', 'Bad', 'Sorry']
class Languages extends Component {
state = {
languages: [{
id: 1,
value: "XXX",
xp: 1.5
},
{
id: 2,
value: "CCC",
xp: 1
},
{
id: 3,
value: "AAA",
xp: 2
}
]
}
render() {
let {
languages
} = this.state;
return (
<div className="languages">
<ProgressBar
programming={languages}
className="languagesDisplay"
levels={levels}
title="Languages"
/>
</div>
);
}
}
export default Languages;
After it, you need to import it in the file where you want to access it:
import {levels} from '/path/to/file';
I'm trying to use an array of dictionaries in python as arguement to a custom dash component and use it as array of objects
in python :
audioList_py = [
{
"name": "random",
"singer": 'waveGAN\'s music',
"cover":
'link_1.jpg',
"musicSrc":
'link_1.mp3',
},
{
"name": "random",
"singer": 'waveGAN\'s music',
"cover":
'link_2.jpg',
"musicSrc":
'link_2.mp3',
},
... etc
]
in Javascript:
audioList1_js = [
{
name: "random",
singer: 'waveGAN\'s music',
cover:'link_1.jpg',
musicSrc: 'link_1.mp3',
},
{
name: "random",
singer: 'waveGAN\'s music',
cover: 'link_2.jpg',
musicSrc: 'link_2.mp3',
},
... etc
]
Here is snippet of javascript code of the dash custom component:
export default class MusicComponent extends Component {
render() {
const {id, audioLists} = this.props;
return (
<div>
<h1>{id}</h1>
<ReactJkMusicPlayer audioLists={audio_list}/>,
</div>
);
}
}
MusicComponent.defaultProps = {};
MusicComponent.propTypes = {
/**
* The ID used to identify this component in Dash callbacks.
*/
audios: PropTypes.array,
id: PropTypes.string,
};
And using the generated component in python:
app = dash.Dash(__name__)
app.layout = html.Div([
music_component.MusicComponent(audios=audioList_py),
html.Div(id='output'),
... etc
])
But I got :
TypeError: The `music_component.MusicComponent` component (version 0.0.1) received an unexpected keyword argument: `audios`Allowed arguments: id
What I am doing wrong ?
Any help or advice will be appreciated, Thanks a lot.
Make sure you run npm run build after you make a change to your custom React component. With those proptypes you shouldn't get that error. If I remove the audios proptype I can reproduce that error.
Besides that you pass a value to the audios property:
music_component.MusicComponent(audios=audioList_py)
but you try to retrieve audioLists from props:
const {id, audioLists} = this.props;
Change this to:
const {id, audios} = this.props;
Demo
export default class MusicComponent extends Component {
render() {
const {id, audios} = this.props;
return (
<div>
<h1>{id}</h1>
<ReactJkMusicPlayer audioLists={audios} />
</div>
);
}
}
MusicComponent.defaultProps = {};
MusicComponent.propTypes = {
/**
* The ID used to identify this component in Dash callbacks.
*/
id: PropTypes.string,
audios: PropTypes.array,
};
Issue fixed, I should run : npm run build:backends to generate the Python, R and Julia class files for the components, but instead I was executing npm run build:js and this command just generate the JavaScript bundle (which didn't know about the new props).
And set the audios property in the component to be like so:
MusicComponent.defaultProps = {audios: audioList1};
MusicComponent.propTypes = {
id: PropTypes.string,
audios: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.string)).isRequired
};
I'm new to React so this might be a very silly question but I just can't seem to get it.
I have a form which has a multi select (imported react-select) and when I select multiple options I want to sent it as an array of objects. Instead I'm sending it as an array of array of objects. I want help to sent it as an array of objects instead of array of array of objects.
import React from "react"
import Select from 'react-select'
class Skills extends React.Component{
state = {
skill: []
}
skillsDropdown = [
{ value: 'React', label: 'React' },
{ value: 'Vue', label: 'Vue' },
{ value: 'Angular', label: 'Angular' },
{ value: 'Java', label: 'Java' },
]
handleChangeSkills = skill => {
this.setState({
skill
})
console.log(skill);
}
handleSubmit = (event) => {
event.preventDefault();
const userSkill= {
skills: this.state.skill.map((item) => [{
skillName: item.value
}])
}
axios.post("http://localhost:8080/api", userSkill)
.then(response => {
console.log("User profile details from backend: ", response.data);
})
}
render(){
return (
<>
<h2>Skills</h2>
<label>Please include 3-5 of your top skills to help you stand out.</label>
<label>
<Select
options={this.state.skillsDropdown}
onChange={this.handleChangeSkills}
isMulti=true
value={skill}
/>
</label>
<button className="Next" onSubmit={this.handleSubmit}>Submit</button>
</>
)
}
}
export default Skills;
When i'm submitting the data I'm getting is
skills: [[{skillName: "React"}], [{skillName: "Vue"}]]
What I want is this
skills: [{skillName: "React"}, {skillName: "Vue"}]
Can anyone please help me with this.
TIA
The culprit is in your map function. Map automatically adds all its results to an array, and you're adding another array around it.
const userSkill= {
// culprit here
skills: this.state.skill.map((item) => [{
skillName: item.value
}])
}
Removing the [] will fix it.
const userSkill= {
skills: this.state.skill.map((item) => ({
skillName: item.value
}))
}
My component is supposed to retrieve the data for courses when the component mounts. The problem that I have is that whether I use the course Id or the course title as the key, I get the following error:
index.js:1 Warning: Each child in a list should have a unique "key" prop.
I have looked through the react docs, here on Stack Overflow, and tried different ways to get it to work. The only way I can get it to partially work is by adding an index as a parameter for map. When I use this method, I run into another problem and that is, it stops after the first iteration, even though there are 10 items. How can I fix this?
Here is my code:
CoursesPage.js
import React from 'react';
import { connect } from 'react-redux';
import * as courseActions from '../../redux/actions/courseActions';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
class CoursesPage extends React.Component {
componentDidMount() {
this.props.actions.loadCourses().catch(error => {
alert("Loading courses failed" + error);
});
}
render() {
return (
<>
<h2>Courses</h2>
{this.props.courses.map((course, index) => (
<div key={course[index].title}>{course[index].title}</div>
))}
</>
);
}
}
CoursesPage.propTypes = {
courses: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
}
function mapStateToProps(state) {
return {
courses: state.courses
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(courseActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage);
My mock data:
const courses = [
{
id: 1,
title: "Securing React Apps with Auth0",
slug: "react-auth0-authentication-security",
authorId: 1,
category: "JavaScript"
},
{
id: 2,
title: "React: The Big Picture",
slug: "react-big-picture",
authorId: 1,
category: "JavaScript"
},
{
id: 3,
title: "Creating Reusable React Components",
slug: "react-creating-reusable-components",
authorId: 1,
category: "JavaScript"
},
{
id: 4,
title: "Building a JavaScript Development Environment",
slug: "javascript-development-environment",
authorId: 1,
category: "JavaScript"
},
{
id: 5,
title: "Building Applications with React and Redux",
slug: "react-redux-react-router-es6",
authorId: 1,
category: "JavaScript"
},
{
id: 6,
title: "Building Applications in React and Flux",
slug: "react-flux-building-applications",
authorId: 1,
category: "JavaScript"
},
{
id: 7,
title: "Clean Code: Writing Code for Humans",
slug: "writing-clean-code-humans",
authorId: 1,
category: "Software Practices"
},
{
id: 8,
title: "Architecture Applications for the Real World",
slug: "architecting-applications-dotnet",
authorId: 1,
category: "Software Architecture"
},
{
id: 9,
title: "Becoming an Outlier: Reprogramming the Developer Mind",
slug: "career-reboot-for-developer-mind",
authorId: 1,
category: "Career"
},
{
id: 10,
title: "Web Component Fundamentals",
slug: "web-components-shadow-dom",
authorId: 1,
category: "HTML5"
}
];
const authors = [
{ id: 1, name: "Cory House" },
{ id: 2, name: "Scott Allen" },
{ id: 3, name: "Dan Wahlin" }
];
const newCourse = {
id: null,
title: "",
authorId: null,
category: ""
};
module.exports = {
newCourse,
courses,
authors
};
Edit:
I am using Redux Thunk.
Here is my actionType.js file:
export const CREATE_COURSE = "CREATE_COURSE";
export const LOAD_COURSES_SUCCESS = "LOAD_COURSES_SUCCESS";
Here is my CourseActions.js file:
import * as types from './actionTypes';
import * as courseApi from "../../api/courseApi";
export function createCourse(course) {
return { type: types.CREATE_COURSE, course };
}
export function loadCourseSuccess(courses) {
return { type: types.LOAD_COURSES_SUCCESS, courses };
}
export function loadCourses() {
return function (dispatch) {
return courseApi.getCourses().then(courses => {
dispatch(loadCourseSuccess(courses));
}).catch(error => {
throw error;
})
}
}
Here is my courseReducer.js file:
import * as types from '../actions/actionTypes';
export default function courseReducer(state = [], action) {
switch (action.type) {
case types.CREATE_COURSE:
return [...state, { ...action.course }];
case types.LOAD_COURSES_SUCCESS:
return [...state, { ...action.courses }];
default:
return state;
}
}
Any help would be appreciated.
Thanks.
P.S. I know that you should use Id for key. But the way it has to be done for now is using the title of the course as the key.
With your edits, I think we can more effectively help you. For future, it would be beneficial to post an example of your code not working on https://codesandbox.io/
Also to help yourself out when you debug, isolate the react component from the use of redux. This will allow you to ensure your react component renders when given data, then focus on getting redux to provide you the data your react component has.
You can do this by first defining mockdata inside your react component, then moving that mock data to your reducer, then finally replacing the mock data with the live api call.
On to the code:
You have two issues: the first is that you want to index into the array courses but instead due to a typo, you are actually using the property accessor into the object course
key={course[index].title}
As your question states you must use the title as the key simply change the div to be:
<div key={course.title}>{course.title}</div> and your code should work as expected.
Once you have addressed that, then re-enable loading data from your API call using Redux, and you can address the issues with Redux.
Looking at your reducer, you have a clear bug, and depending on your use case, a potential bug:
case types.LOAD_COURSES_SUCCESS:
return [...state, { ...action.courses }];
action.courses is an array, and the code is creating a new array that contains all the elements of the previous array state and adding a new object, which contains the contents of a destructured array.courses array.
Which that does is effectively append a single object to your array, and the object is comprised of elements from your courses array. The item index becomes the key, and the item itself is the value.
You can visualize it here: https://codesandbox.io/s/divine-pond-nr3j8
Instead you want
return [...state, ...action.courses];
The second, potential bug, is that you are actually appending the results of the courses api. Which for subsequent calls to load courses, you will duplicate the data. That may be what you want, but I am going to assume that is not what you want.
So instead, your LOAD_COURSES_SUCCESS case should be rewritten to simply be:
return [...action.courses];
Since each course has a unique ID field, the problem could be solved by using the id as a key
render() {
return (
<>
<h2>Courses</h2>
{this.props.courses.map(course => (
<div key={course.id}>{course.title}</div>
))}
</>
);
}
The issue is here:
{this.props.courses.map((course, index) => (
<div key={course[index].title}>{course[index].title}</div>
))}
//course in itself is data
Solution:
{this.props.courses.map((course, index) => (
<div key={`${course.title}-${index}`}>{course.title}</div>
))}
Better way would always to take care unique id for key
Here's the code snippet for render method.
Give it a try.
*Update = use course title as key
render() {
const courseList = this.props.courses.map(course => (
<div key={course.title}></div>
));
return (
<>
<h2>Courses</h2>
<div>{courseList}</div>
</>
);
}
There are many ways you can pass the key when it is iterating...
var courses = this.state.courses.map(function(course, index) {
return(
<div key={index}>
<div key={course.title} id={course.title}>
<h2 key={"header"+course.title}>{course.title}</h2>
</div>
</div>
)
});
But you should try to pass a unique id rather than a string.
I have a "FormBuilder" component that iterates through a list of objects and iterates an input based on the listed type.
I have an 'enum' (I know it's not really an enum, because it's Javascript), that associates a constant term with a certain component name, like this:
const InputTypes = Object.freeze({
TEXT: 'TextInput',
PASSWORD: 'PasswordInput',
SUBMIT: 'SubmitInput',
RESET: 'ResetInput',
RADIO: 'RadioInput',
CHECKBOX: 'CheckboxInput',
BUTTON: 'ButtonInput',
DATE: 'DateInput',
EMAIL: 'EmailInput',
NUMBER: 'NumberInput',
TIMEINTERVAL: 'TimeIntervalInput'
});
Each of the strings is the name of a component. I won't list them all, but here's a sample, 'TextInput':
function TextInput(props) {
return (
<React.Fragment>
<label>
{props.name}:
<input
type='text'
name={props.name.toLowerCase()}
value={props.value}
onChange = {props.handlers.handleChange}
{...props.attributes}
/>
</label>
</React.Fragment>
);
}
export default TextInput;
Now I have a 'FormBuilder' component that iterates through a bunch of objects containing a type property:
render() {
return <form id={this.props.id}>
{
this.props.inputFields.map(input=>{
const propsWithKeyAndHandlers = Object.assign({
key:"" + this.props.task._id + input.props.name,
handlers:this.props.handlers,
value: this.props.task[input.props.name]}
,input.props);
return input.content?
React.createElement(input.type, propsWithKeyAndHandlers, input.content):
React.createElement(input.type, propsWithKeyAndHandlers);
})
}
</form>
An example of the kind of data that gets passed to this component would be:
inputFields: [
{props: {name:'name', },type:InputTypes.TEXT},
{props: {name: 'description'}, type:InputTypes.TEXT},
{props: {name:'deadline'},type:InputTypes.DATE},
{props:{name:'startDate'},type:InputTypes.DATE},
{props: {name:'external'},type:InputTypes.CHECKBOX},
{props: {name:'estTime'},type:InputTypes.NUMBER}
],
So here's a sample of what I see in the React developer console when I run this:
As you can see it's rendering the correct component, but it's just listing the component name, not what's in the component.
How do I get React to render what's in the component as I'd expect it to?
Your InputTypes object needs to reference the actual components, not a string. For example
import TextInput from "./TextInput.jsx";
...
const InputTypes = Object.freeze({
TEXT: TextInput,
...
});
Your 'enum' links to strings that just happen to have the same name as your components, it doesn't link to the actual components. You can't use strings to refer to the component name by default. There's no magical, inherent link between a string and a component with that same name.