unable to set prop on nested component - javascript

I'm learning ReactJS and using this library https://github.com/salesforce/design-system-react.
I'm attempting to use a component I created SelectCell. It's being used two times. I'd like to pass it a prop selectedOption and in the first instance pass it a property originating from my state, a property selectedSectionId and the second time the component is used set selectedOption to be selectedQuestionId.
The issue is the library obfuscates some of the logic away and I'm not well versed enough in react to understand what to do. I set items on the DataTable component and I know the children components have access to item in props. I'm getting the error TypeError: Cannot read property 'selectedSectionId' of undefined My component is below:
import React from 'react';
import {Button,DataTable,DataTableColumn,DataTableCell,Dropdown,DataTableRowActions} from '#salesforce/design-system-react';
const ParameterDataTableCell = ({ children, ...props }) => (
<DataTableCell title={children} {...props}>
<input type='text' className='slds-input' value={props.item.parameterName} />
</DataTableCell>
);
ParameterDataTableCell.displayName = DataTableCell.displayName;
const SelectCell = ({ children,...props }) => (
<DataTableCell {...props}>
<div>
<Dropdown
align='left'
checkmark={false}
iconCategory='utility'
iconName='down'
iconPosition='right'
label={setPicklistLabel(props.allOptions,props.type,props.item,props.selectedOptionId)}
options={props.allOptions}
value={props.item.sectionName}>
</Dropdown>
</div>
</DataTableCell>
);
const setPicklistLabel = (allOptions,picklistType,item,selectedOptionId) => {
const foundOption = allOptions.find((thisOption) => selectedOptionId===thisOption.id);
return foundOption ? foundOption.label : 'Select an Option';
}
SelectCell.displayName = DataTableCell.displayName;
class ParameterTable extends React.Component {
static displayName = 'ParameterTable';
state = {
paramRows: [
{
parameterName: 'param1',
selectedSectionId: '001441094',
selectedQuestionId: '00ri23or231441094'
},
{
parameterName: 'param2',
selectedSectionId: '001441094',
selectedQuestionId: '00ri23or231441094'
}
],
};
addRow = () => {
const newRow = {'parameterName':'','selectedSectionId':'','selectedQuestionId':''};
const rows = this.state.paramRows;
rows.push(newRow);
this.setState({items:rows});
};
render() {
return (
<div>
<DataTable
items={this.state.paramRows}
className='slds-m-top_large'
>
<DataTableColumn
label='Parameter Name'
primaryColumn
property='parameterName'
>
<ParameterDataTableCell />
</DataTableColumn>
<DataTableColumn
label='Section Name'
property='sectionName'
>
<SelectCell
allOptions={this.props.serverData.allSections}
selectedOptionId={this.props.item.selectedSectionId}/>
</DataTableColumn>
<DataTableColumn
label='Question Name'
property='questionName'
>
<SelectCell
allOptions={this.props.serverData.allQuestions}
selectedOptionId={this.props.item.selectedQuestionId}/>
</DataTableColumn>
</DataTable>
<Button
iconCategory='utility'
iconName='add'
iconPosition='right'
label='Add Parameter'
onClick={this.addRow}
/>
</div>
);
}
}
export default ParameterTable;

Well, from first glance, it seems like you missed out using the constructor(props) and super(props) lines.
class ParameterTable extends React.Component {
static displayName = 'ParameterTable';
constructor(props) {
super(props);
this.state = {
paramRows: [
{
parameterName: 'param1',
selectedSectionId: '001441094',
selectedQuestionId: '00ri23or231441094'
},
{
parameterName: 'param2',
selectedSectionId: '001441094',
selectedQuestionId: '00ri23or231441094'
}
],
};
}

Related

How do you update state in functional components in React?

I'm running into the issue where I have created a functional component to render a dropdown menu, however I cannot update the initial state in the main App.JS. I'm not really sure how to update the state unless it is in the same component.
Here is a snippet of my App.js where I initialize the items array and call the functional component.
const items = [
{
id: 1,
value:'item1'
},
{
id: 2,
value:'item2'
},
{
id: 3,
value:'item3'
}
]
class App extends Component{
state = {
item: ''
}
...
render(){
return{
<ItemList title = "Select Item items= {items} />
And here is my functional componenet. Essentially a dropdown menu from a YouTube tutorial I watched (https://www.youtube.com/watch?v=t8JK5bVoVBw).
function ItemList ({title, items, multiSelect}) {
const [open, setOpen] = useState (false);
const [selection, setSelection] = useState([]);
const toggle =() =>setOpen(!open);
ItemList.handleClickOutside = ()=> setOpen(false);
function handleOnClick(item) {
if (!selection.some(current => current.id == item.id)){
if (!multiSelect){
setSelection([item])
}
else if (multiSelect) {
setSelection([...selection, item])
}
}
else{
let selectionAfterRemoval = selection;
selectionAfterRemoval = selectionAfterRemoval.filter(
current =>current.id == item.id
)
setSelection([...selectionAfterRemoval])
}
}
function itemSelected(item){
if (selection.find(current =>current.id == item.id)){
return true;
}
return false;
}
return (
<div className="dd-wraper">
<div tabIndex={0}
className="dd-header"
role="button"
onKeyPress={() => toggle(!open)}
onClick={() =>toggle(!open)}
onChange={(e) => this.setState({robot: e.target.value})}
>
<div className="dd-header_title">
<p className = "dd-header_title--bold">{title}</p>
</div>
<div className="dd-header_action">
<p>{open ? 'Close' : 'Open'}</p>
</div>
</div>
{open && (
<ul className ="dd-list">
{item.map(item =>(
<li className="dd-list-item" key={item.id}>
<button type ="button"
onClick={() => handleOnClick(item)}>
<span>{item.value}</span>
<span>{itemSelected(item) && 'Selected'}</span>
</button>
</li>
))}
</ul>
)}
</div>
)
}
const clickOutsideConfig ={
handleClickOutside: () => RobotList.handleClickOutside
}
I tried passing props and mutating the state in the functional component, but nothing gets changed. I suspect that it needs to be changed in the itemSelected function, but I'm not sure how. Any help would be greatly appreciated!
In a function component, you have the setters of the state variables. In your example, you can directly use setOpen(...) or setSelection(...). In case of a boolean state variable, you could just toggle by using setOpen(!open). See https://reactjs.org/docs/hooks-state.html (Chapter "Updating State") for further details.
So you need to do something like below . Here we are passing handleChange in parent Component as props to the child component and in Child Component we are calling the method as props.onChange
Parent Component:
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
value :''
}
}
handleChange = (newValue) => {
this.setState({ value: newValue });
}
render() {
return <Child value={this.state.value} onChange = {this.handleChange} />
}
}
Child Component:
function Child(props) {
function handleChange(event) {
// Here, we invoke the callback with the new value
props.onChange(event.target.value);
}
return <input value={props.value} onChange={handleChange} />
}

Loop over all instances of component, log each state

I'm building out a simple drum machine application using ReactJS and could use some help understanding how to loop through all instances of a component while outputting each instance's state.
The application UI shows 16 columns of buttons, each containing 4 unique drum rows. There is a "SixteenthNote.js" component which is essentially on column containing each "Drum.js" instance. In the "DrumMachine.js" module, I am outputting "SixteenthNote.js" 16 times to display one full measure of music. When you click on a drum button, that drum's value is pushed into the SixteenthNote' state array. This is all working as intended.
The last part of this is to create a "Play.js" component which, when clicked, will loop through all of the SixteenthNote instances and output each instance's state.
Here is the "DrumMachine.js" module
class DrumMachine extends Component {
constructor(props) {
super(props);
this.buildKit = this.buildColumns.bind(this);
this.buildLabels = this.buildLabels.bind(this);
this.buildAudio = this.buildAudio.bind(this);
this.state = {
placeArray: Array(16).fill(),
drumOptions: [
{type: 'crash', file: crash, title: 'Crash'},
{type: 'kick', file: kick, title: 'Kick'},
{type: 'snare', file: snare, title: 'Snare'},
{type: 'snare-2', file: snare2, title: 'Snare'}
]
}
}
buildLabels() {
const labelList = this.state.drumOptions.map((sound, index) => {
return <SoundLabel title={sound.title} className="drum__label" key={index} />
})
return labelList;
}
buildColumns() {
const buttonList = this.state.placeArray.map((object, index) => {
return <SixteenthNote columnClassName="drum__column" key={index} drumOptions={this.state.drumOptions}/>
});
return buttonList;
}
buildAudio() {
const audioList = this.state.drumOptions.map((audio, index) => {
return <Audio source={audio.file} drum={audio.type} key={index}/>
})
return audioList;
}
render() {
return (
<div>
<div className={this.props.className}>
<div className="label-wrapper">
{this.buildLabels()}
</div>
<div className="drum-wrapper">
{this.buildColumns()}
</div>
</div>
<div className="audio-wrapper">
{this.buildAudio()}
</div>
</div>
)
}
}
Here is "SixteenthNote.js" module
class SixteenthNote extends Component {
constructor(props) {
super(props);
this.buildColumn= this.buildColumn.bind(this);
this.buildDrumOptions = this.buildDrumOptions.bind(this);
this.updateActiveDrumsArray = this.updateActiveDrumsArray.bind(this);
this.state = {
activeDrums: []
}
}
buildDrumOptions() {
return this.props.drumOptions;
}
updateActiveDrumsArray(type) {
let array = this.state.activeDrums;
array.push(type);
this.setState({activeDrums: array});
}
buildColumn() {
const placeArray = this.buildDrumOptions().map((button, index) => {
return <Drum buttonClassName="drum__button" audioClassName="drum__audio" type={button.type} file={button.file} key={index} onClick={() => this.updateActiveDrumsArray(button.type)}/>
})
return placeArray;
}
render() {
return (
<div className={this.props.columnClassName}>
{this.buildColumn()}
</div>
)
}
}
Here is the "Drum.js" module
class Drum extends Component {
constructor(props) {
super(props);
this.clickFunction = this.clickFunction.bind(this);
this.state = {
clicked: false
}
}
drumHit(e) {
document.querySelector(`.audio[data-drum=${this.props.type}]`).play();
this.setState({clicked:true});
}
clickFunction(e) {
this.state.clicked === false ? this.drumHit(e) : this.setState({clicked:false})
}
render() {
const drumType = this.props.type;
const drumFile = this.props.file;
const buttonClasses = `${this.props.buttonClassName} drum-clicked--${this.state.clicked}`
return (
<div onClick={this.props.onClick}>
<button className={buttonClasses} data-type={drumType} onClick={this.clickFunction}></button>
</div>
)
}
}
You will need to contain the information about the activeDrums in your DrumMachine component.
That means:
In your DrumMachine component you create the state activeDrums like you have in your SixteenthNote.js. You will need to put your updateActiveDrumsArray function to your drumMachine component as well.
Then you pass this function to your SixteenthNote component like:
<SixteenthNote columnClassName="drum__column" key={index} drumOptions={this.state.drumOptions} onDrumsClick={this.updateActiveDrumsArray} />
After doing so, you can access that function via props. So, in your SixteenthNote component it should look like:
<Drum buttonClassName="drum__button" audioClassName="drum__audio" type={button.type} file={button.file} key={index} onClick={() => this.props.onDrumsClick(button.type)}/>
(Don't forget to get rid of the unneccessary code.)
With this, you have your activeDrums state in DrumMachine containing all the active drums. This state you can then send to your play component and do the play action there.

Toggling visibility of array of stateless react components

I am trying to simply map over some data returned from an api and create a stateless component for each object returned. I want to be able to click on any of the components to toggle visibility of the rest of its data.
I have tried numerous ways to do it and keep hitting a brick wall, i've also scoured stack overflow and cannot seem to find an answer.
I have gotten it working by making them individual class components, however it seems like a lot of unnecessary code for just a toggle functionality.
Thank you in advance for any help or insight, here is a quick breakdown of what I have currently.
For clarification this is a simple app for me to learn about using react and an external api, it is not using redux.
fetched users in state of class component
class PersonList extends Component {
constructor(props) {
super(props);
this.state = {
resource: []
};
}
async componentDidMount() {
let fetchedData = await API_Call("people");
this.setState({ resource: fetchedData.results });
while (fetchedData.next) {
let req = await fetch(fetchedData.next);
fetchedData = await req.json();
this.setState({
resource: [...this.state.resource, ...fetchedData.results]
});
}
}
}
Then map over the results and render a component for each result
render() {
const mappedPeople = this.state.resource.map((person, i) => (
<Person key={i} {...person} />
));
return <div>{mappedPeople}</div>;
}
Is there i can make each person component a stateless component with the ability to click on it and display the rest of the data? Here is what I have currently.
class Person extends Component {
constructor(props) {
super(props);
this.state = {
visibility: false
};
}
toggleVisible = () => {
this.setState(prevState => ({
visibility: !prevState.visibility
}));
};
render() {
return (
<div>
<h1 onClick={this.toggleVisible}>{this.props.name}</h1>
{this.state.visibility && (
<div>
<p>{this.props.height}</p>
</div>
)}
</div>
);
}
}
Again thanks in advance for any insight or help!
You could keep an object visible in your parent component that will have keys representing a person index and a value saying if the person is visible or not. This way you can toggle the person's index in this single object instead of having stateful child components.
Example
class PersonList extends Component {
constructor(props) {
super(props);
this.state = {
resource: [],
visible: {}
};
}
// ...
toggleVisibility = index => {
this.setState(previousState => {
const visible = { ...previousState.visibile };
visible[index] = !visible[index];
return { visible };
});
};
render() {
const mappedPeople = this.state.resource.map((person, i) => (
<Person
key={i}
{...person}
visible={this.state.visible[i]}
onClick={() => this.toggleVisibility(i)}
/>
));
return <div>{mappedPeople}</div>;
}
}
const Person = (props) => (
<div>
<h1 onClick={props.onClick}>{props.name}</h1>
{props.visible && (
<div>
<p>{props.height}</p>
</div>
)}
</div>
);
Similar idea with #Tholle but a different approach. Assuming there is an id in the person object we are changing visibles state and toggling ids.
class PersonList extends React.Component {
constructor(props) {
super(props)
this.state = {
resource: this.props.persons,
visibles: {},
}
}
toggleVisible = id => this.setState( prevState => ({
visibles: { ...prevState.visibles, [id]: !prevState.visibles[id] },
}))
render() {
const mappedPeople =
this.state.resource.map((person, i) =>
<Person
key={person.id}
visibles={this.state.visibles}
toggleVisible={this.toggleVisible}
{...person}
/>
)
return (
<div>
{mappedPeople}
</div>
)
}
}
const Person = (props) => {
const handleVisible = () =>
props.toggleVisible( props.id );
return (
<div>
<h1 onClick={handleVisible}>
{props.name}</h1>
{props.visibles[props.id] &&
<div>
<p>{props.height}</p>
</div>
}
</div>
);
}
const persons = [
{ id: 1, name: "foo", height: 10 },
{ id: 2, name: "bar", height: 20 },
{ id: 3, name: "baz", height: 30 },
]
const rootElement = document.getElementById("root");
ReactDOM.render(<PersonList persons={persons} />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can make sure your "this.state.resource" array has a visibility flag on each object:
this.state.resource = [
{ ..., visibility: true },
{ ..., visibility: false}
...
];
Do this by modifying your fetch a little bit.
let fetchedData = await API_Call("people");
this.setState({
resource: fetchedData.results.map(p => ({...p, visiblity: true}))
});
Merge your Person component back into PersonList (like you are trying to do), and on your onclick, do this:
onClick={() => this.toggleVisible(i)}
Change toggleVisible() function to do the following.
toggleVisible = (idx) => {
const personList = this.state.resource;
personList[idx].visibility = !personList[idx].visibility;
this.setState({ resource: personList });
}
So now, when you are doing:
this.state.resource.map((person, i) => ...
... you have access to "person.visibility" and your onclick will toggle the particular index that is clicked.
I think that directly answers your question, however...
I would continue with breaking out Person into it's own component, it really is good practice!
Other than better organization, one of the main reason is to avoid lamdas in props (which i actually did above). Since you need to do an onClick per index, you either need to use data attributes, or actually use React.Component for each person item.
You can research this a bit here:
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
BTW you can still create "components" that aren't "React.Component"s like this:
import React from 'react';
const Person = ({ exProp1, exProp2, exProp3}) => {
return <div>{exProp1 + exProp2 + exProp3}</div>
}
Person.propTypes = {
...
}
export default Person;
As you can see, nothing is inheriting from React.Component, so you are getting the best of both worlds (create components without creating "Components"). I would lean towards this approach, vs putting everything inline. But if your application is not extremely large and you just want to get it done, going with the first approach isn't terribly bad.

RN - Change parents' screenProps

I'm working on a React Native menu with a StackNavigator. If the user press a ListItem an id should be passed to all other Tabs in this menu, so the data can get fetched from an API. I tried to use screenProps to pass the data. Unfortunately I wasn't able to reset the value, when pressing a ListItem.
export default class Index extends Component {
constructor(props){
super(props);
}
render() {
return (
<OrderScreen
screenProps={ { Number: 123 } }
/>
);
}
}
In the child components I can access the prop but not reassign it:
export default class ListThumbnailExample extends Component
{
constructor(props)
{
super(props);
const{screenProps} = this.props;
this.state = { epNummer: screenProps.Number };
}
render()
{
return (
<Content>
<List>
{
this.state.orders.map(data => (
<ListItem key = {data.Number}
onPress = {() =>
{
this.props.screenProps.Number = data.Number;
this.props.navigation.navigate('Orders')
}
}
<Text>{ data.name }</Text>
</ListItem >
))
}
</List>
</Content >
);
}
}
Thank you!
In React and React-native props are immutable by design :
https://reactjs.org/docs/components-and-props.html#props-are-read-only
In your case if you want to pass screen-specific data you may wanna try passing them in the params of the navigation.navigate() function like this :
this.props.navigation.navigate('Orders',data.Number)
you can then access them in "Orders" screen from : props.navigation.state.params
More information here : https://reactnavigation.org/docs/params.html

How to remove an instance of a React component class instantiated by its parent's state?

(Pardon the verbose question. I'm brand new to React and ES6, and I'm probably overly-convoluting this.)
I am writing an app that contains a button component. This button calls a method onAddChild that creates another component of class ColorModule by adding a value to an array stored in the App's state.
In each newly created ColorModule, I want to include another button that will remove the module. Since this component is created by an array.map method, my thought is that if I can find the index of the array item that corresponds with the component and use that index in array.splice then perhaps that component will be removed (untested theory). That said, I'm not really sure how to find the index where I would use this in my onRemoveModule method.
Two part question: 1) How would I go about finding the index of the array item in my state, and 2) if I'm completely off base or there's a better way to do this altogether, what does that solution look like?
imports...
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(1) });
}
onRemoveModule( e ) {
e.preventDefault();
...¯\_(ツ)_/¯
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} /> /* Add module button lives here */
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule}
key={index}
moduleId={'colorModule' + index}
/>
); /* Remove module button would live in the module itself */
}
)}
</div>
</div>
);
}
}
export default App;
Well this part is pretty easy, all you need to do is pass the index as prop to the ColorModule component and when calling the onRemove method in it you could pass it back to the onRemoveModule. However react optimizes based on keys and its a really good idea to have a unique id given to each module instance.
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(uuid()) }); //uuid must return a unique id everytime to be used as component key
}
onRemoveModule( index ) {
// now with this index you can update the moduleList
}
render() {
const { className } = this;
return (
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
index={index}
onRemove={this.onRemoveModule}
key={delta}
moduleId={'colorModule' + delta}
/>
);
}
)}
</div>
);
}
}
Now in ColorModule component
class ColorModule extends React.Component {
onRemoveClick=() => {
this.props.onRemove(this.props.index);
}
}
Check this answer for more details on how to pass data from Child component to Parent
I ended up solving this problem using some of the guidance here from #ShubhamKhatri (didn't know about unique ID generation!), but I took a slightly different approach and handled the solution using state manipulation in App without needing a new method in my ColorModule component. I also never knew about currying in ES6, so that discovery made passing in the index values needed to manipulate my state array possible
If I'm off-base here or being inefficient, I'm definitely still open to feedback on a better way!
class App extends Component {
constructor(props) {
super(props);
this.state = {
moduleList: [{ id: UniqId(), removeModule: false }],
};
this.onAddChild = this.onAddChild.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({
moduleList: moduleList.concat({
id: UniqId(),
removeModule: false,
}),
});
}
onRemoveModule = ( i, arr ) => (e) => {
const moduleList = this.state.moduleList;
e.preventDefault();
moduleList[i].removeModule = true;
this.setState({ moduleList: moduleList });
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} />
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
if ( !this.state.moduleList[index].removeModule ) {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule( index, this.state.moduleList )}
index={index}
key={delta.id}
moduleId={'colorModule' + delta}
/>
);
}
}
)}
</div>
</div>
);
}
}

Categories