I have an component that renders different types of fields called Item. Item may render a select box with a list of Users or a list of Inventory. I have two containers: one for Users and another for Inventory. I originally thought to nest my containers but that appears to freeze my react app. Inventories and Users containers are identical except that one container holds inventory items and the other holds users.
Here is the Users container:
import React, { Component } from 'react';
class UsersContainer extends Component{
constructor(props){
super(props);
this.state = {
users: []
}
}
componentDidMount(){
//put api call here
this.setState({users: [{id: 1, name: "Test Name", email: "test#yahoo.com"}, {id: 2, name: "John Doe", email: "johndoe#gmail.com"}, {id: 3, name: "Jane Doe", email: "janedoe#yahoo.com"}]})
}
render(){
return(
<div className="users-container">
{React.Children.map(this.props.children, child => (
React.cloneElement(child, {...this.props, users: this.state.users })
))}
</div>
)
}
}
export default UsersContainer;
I originally tried to nest the containers but this causes React to freeze:
<UsersContainer>
<InventoriesContainer>
{this.props.items.map(i => (
<Item name={i.name} />
))}
</InventoriesContainer>
</UsersContainer>
Item looks something like this:
function elementUsesInvetory(inventories){
//returns selectbox with list of inventory
}
function elementUsesUsers(users){
//returns selectbox with list of users
}
function Item(props){
render(){
return(
<>
{elementUsesUsers(props.inventories)}
{elementUsesInventory(props.users)}
</>
);
}
}
How can I provide the data from UsersContainer and InventoriesContainer to the Item component?
Merging them into one component would avoid a lot of confusion. If you still want to nest them, you might want to pass the props by prop-drilling or by using the context API. React.cloneElement isn't preferred for nested child components. More on that here
You can pass down the data with the help of React's context API. The UsersContainer component holds the Provider and passes users down to Inventories
The Inventories will then pass on the users and inventories as props to the Items component. I'm not sure if you need separate functions for the select boxes but I've added them in the demo anyway.
const MyContext = React.createContext();
class UsersContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
users: []
};
}
componentDidMount() {
//put api call here
this.setState({
users: [
{ id: 1, name: "Test Name", email: "test#yahoo.com" },
{ id: 2, name: "John Doe", email: "johndoe#gmail.com" },
{ id: 3, name: "Jane Doe", email: "janedoe#yahoo.com" }
]
});
}
render() {
return (
<div className="users-container">
<MyContext.Provider value={this.state.users}>
{this.props.children}
</MyContext.Provider>
</div>
);
}
}
class Inventories extends React.Component {
static contextType = MyContext;
constructor(props) {
super(props);
this.state = {
inventories: []
};
}
componentDidMount() {
//put api call here
this.setState({
inventories: [
{ id: 1, name: "Test Name", email: "test#yahoo.com" },
{ id: 2, name: "John Doe", email: "johndoe#gmail.com" },
{ id: 3, name: "Jane Doe", email: "janedoe#yahoo.com" }
]
});
}
render() {
return (
<div className="inventory-container">
{React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {
...this.props,
users: this.context,
inventories: this.state.inventories
});
})}
</div>
);
}
}
function Items(props) {
function usersSelect(items) {
return (
<select>
{items.map((item) => (
<option key={"user"+item.id} value="{item.id}">
{item.name}
</option>
))}
</select>
);
}
function inventoriesSelect(items) {
return (
<select>
{items.map((item) => (
<option key={item.id} value="{item.id}">
{item.name}
</option>
))}
</select>
);
}
return (
<div>
<h2>users</h2>
{usersSelect(props.users)}
<h2>inventories</h2>
{inventoriesSelect(props.inventories)}
</div>
);
}
function App() {
return (
<div>
<UsersContainer>
<Inventories>
<Items />
</Inventories>
</UsersContainer>
</div>
);
}
ReactDOM.render(<App/>, 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>
I good approach would be to put the state in common between those components in a level up in the tree component.
So what are you trying to do:
<UsersContainer>
<InventoriesContainer>
{this.props.items.map(i => (
<Item name={i.name} />
))}
</InventoriesContainer>
</UsersContainer>
Would be:
RealFatherComponent extends Component {
// state that Item will need will be set here
render() {
return (
< UsersContainer **propsShared** >
<Item **propsShared** />
</UsersContainer>
< InventoriesContainer **propsShared** >
<Item **propsShared** /> );
</InventoriesContainer>
}
}
Related
I'm trying to display my AboutPageContent.js to AboutPage.js. I would assume I would have to map it out somehow, but I'm not sure how.
Relevant Code
AboutPage.js
import React from 'react';
// CSS import statements
import '../css/AboutPage.css';
import '../css/App.css';
// Content import Statements
import AboutPageContent from '../content/AboutPageContent.js';
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return(
<div className='about-page'>
<div className='page-header'>{this.state.about.name}</div>
<div>{this.state.about.desc.map(paragraph => <p>{paragraph}</p>)}</div>
</div>
)
}
}
export default About;
AboutPageContent.js
let AboutPageContent = {
about: [{
name: 'About Me',
desc: [
'p1',
'p2',
'p3',
'p4',
'p5',
'p6'
],
id: 1
}]};
export default AboutPageContent;
You have to do 2 maps, 1 for state.about and another one for state.about[i].desc.
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return (
<div className="about-page">
<div className="page-header">{this.state.about.name}</div>
<div>
{this.state.about.map((currAbout) =>
currAbout.desc.map((paragraph, i) => <p key={i}>{paragraph}</p>)
)}
</div>
</div>
);
}
}
Or if you want to display the current about.name, move the <div className='page-header'>... inside this.state.about loop.
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return (
<div className="about-page">
<div>
{this.state.about.map((currAbout, i) => (
<React.Fragment key={i}>
<div className="page-header">{currAbout.name}</div>
{currAbout.desc.map((paragraph, i) => (
<p key={i}>{paragraph}</p>
))}
</React.Fragment>
))}
</div>
</div>
);
}
}
Your this.state.about is an array, so you can't reference the name and desc properties with dot notation. Currently, this.state.about.name would be undefined. this.state.about[0].name would be equal to 'About Me'.
I would simply remove the [ ]s around the this.state.about property. That would make it an object (as opposed to an array) so the rest of your code should work fine. Leave this.state.about.desc as an array so you can use the map method.
You can access the object in about this way state.about[0]
const AboutPageContent = {
about: [
{
name: "About Me",
desc: ["p1", "p2", "p3", "p4", "p5", "p6"],
id: 1,
},
],
};
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return (
<div className="about-page">
<div className="page-header">{this.state.about[0].name}</div>
<div>
{this.state.about[0].desc.map((paragraph) => (
<p>{paragraph}</p>
))}
</div>
</div>
);
}
}
ReactDOM.render(
<About />,
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>
However if you have more than one property in the object and you want to display description by name you could try this
const AboutPageContent = {
about: [
{
name: "About Me",
desc: ["p1", "p2", "p3", "p4", "p5", "p6"],
id: 1,
},
{
name: " Section 2",
desc: ["p12", "p10"],
id: 1,
},
],
};
class About extends React.Component {
state = AboutPageContent;
render() {
return (
<div className="about-page">
<div className="page-header">
{Object.values(this.state)
.flat()
.map((o, i) => (
<React.Fragment key={i}>
<p>{o.name}</p>
{o.desc.map((paragraph) => (
<p>{paragraph}</p>
))}
</React.Fragment>
))}
</div>
</div>
);
}
}
ReactDOM.render(
<About />,
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>
I have a store which gives me array of password objects
For Example :
const passwords = [{
id: 'hjgkjhkjl',
password: 'jhckjdhf',
createdAt: 'jan 3rd, 2019'
},
{
id: 'uygkjhkj',
password: 'kdjhfkjdhf',
createdAt: 'jan 3rd, 2019'
}
]
I can access this passwords in my component as this.props.passwords
I am trying to populate them on UI as a List with button(Update / Confirm) - I am toggling between them using a component level state
Since it is a component level state if I click on one button all the buttons are getting changed
Here is my code
class Passwords extends React.Component {
constructor(props) {
super(props)
this.state = {
disabled: false
}
this.handleUpdate = this.handleUpdate.bind(this)
}
handleUpdate(element) {
// this.context.executeAction(removePassword, element.createdAt)
// this.context.executeAction(fetchPasswords)
this.setState({
disabled: !this.state.disabled,
})
}
render() {
const data = [
{
password: "passwordone",
createdAt: "Jan 2, 2019"
},
{
password: "passwordtwo",
createdAt: "Jan 2, 2019"
}
];
return (
<div>
{data.map((element) => {
return (
<div>
<input value={element.password} disabled={!this.state.disabled}/>
<span style={{marginLeft: "15px"}}> {element.createdAt} </span>
<span style={{marginLeft: "15px"}}>
<button onClick={() => this.handleUpdate(element)}>
{this.state.disabled ? "Confirm" : "Update"}
</button>
</span>
</div>
)
})}
</div>
)
}
}
ReactDOM.render(<Passwords/>, 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>
Need a better approach to solve this
Working example is in the following Link
id are unique for the password items - expected result update/confirm need to be toggled only for clicked button
Thanks in advance
You're having this problem because you assigned both buttons to the same value in your state. By toggling one button and updating that shared state value, it would make sense that both buttons get updated.
What you should do instead is move your buttons down into a child component, where they keep track of their own piece of state. So when you click on one button, it will only change the state for that component.
As sellmeadog said, you should create a parent component that will contain 2 components. Each component will have its own state, and the parent component will give each child its props. This way each button will have its own state, which means its own "disabled". In general it should look like that:
class PasswordsContainer extends React.Component {
constructor(props) {
super(props);
const data = [
{
password: 'passwordone',
createdAt: 'Jan 2, 2019',
},
{
password: 'passwordtwo',
createdAt: 'Jan 2, 2019',
},
];
this.state = {
disabled: false,
data: data,
};
this.handleUpdate = this.handleUpdate.bind(this);
}
handleUpdate(element) {
// this.context.executeAction(removePassword, element.createdAt)
// this.context.executeAction(fetchPasswords)
this.setState({
disabled: !this.state.disabled,
});
}
render() {
return (
<div>
{this.state.data.map(element => {
return <Password data={element} />;
})}
</div>
);
}
}
class Password extends React.Component {
constructor(props) {
super(props);
this.state = {
disabled: false,
data: this.props.data,
};
this.handleUpdate = this.handleUpdate.bind(this);
}
handleUpdate(element) {
this.setState({
disabled: !this.state.disabled,
});
}
render() {
let element = this.state.data;
return (
<div>
<input value={element.password} disabled={!this.state.disabled} />
<span
style={{
marginLeft: '15px',
}}>
{' '}
{element.createdAt}
</span>
<span
style={{
marginLeft: '15px',
}}>
<button onClick={() => this.handleUpdate(element)}>
{this.state.disabled ? 'Confirm' : 'Update'}
</button>
</span>
</div>
);
}
}
ReactDOM.render(<PasswordsContainer />, 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>
please tell me if something is unclear
I am trying to recursively render JSON data to nested list using React. Right now I am using simple data object like this:
[{"id": "1",
"name": "Luke"
},
{"id": "2",
"name": "Jim",
"childNodes":[{
"id": "3",
"name": "Lola"
}]
}]
using this class:
export default class NestedList extends Component {
constructor(props) {
super(props);
this.state = {
visible: true
};
}
toggle = () => {
this.setState({ visible: !this.state.visible });
};
renderChild = (child) => {
if (child.childNodes) {
return (
<ul>
{child.myData.map(item => {
return this.renderChild(item);
})}
</ul>
);
}
else if (child.name) {
return <input type="checkbox"><Child name={child.name}/></input>;
}
return null;
}
render() {
return (
<aside>
<div>
<h4>Data Sets</h4>
<ul>
{this.renderChild(this.props.myData)}
</ul>
</div>
</aside>
);
}
}
which calls a Child class that creates list element:
export default class Child extends Component {
render() {
let {name}=this.props;
return (
<li>{name}</li>
);
}
}
but it doesn't print anything. I have tried removing attribute childNodes altogether and tried to print the list but it doesn't work still. I don't understand where I am doing wrong. I would appreciate some help regarding how to fix this.
You need to map through myData first so the rendering process begins:
<ul>
{this.props.myData.map(data => this.renderChild(data))}
</ul>
Also, on childNodes you need to loop through child.childNodes:
if (child.childNodes) {
return (
<ul>
{child.childNodes.map(node => this.renderChild(node))}
</ul>
);
}
there were couple of issues here:
You passed myData to renderChild which doesn't hold childNodes
property nor name property. Hence none of the conditions were met
(null was returned).
So maybe you should loop through myData and
pass each member of the array to renderChild.
Even if we will pass a valid "child" to the renderChild method,
inside this condition:
if (child.childNodes) {
Again you are using a wrong property:
<ul>
{child.myData.map(item => {
return this.renderChild(item);
})}
</ul>
this should be:
{child.childNodes.map(item => {...
Last thing, You can't nest child elements inside an input element.
so change the layout, maybe like this? :
<input type="checkbox"/>
<Child name={child.name} />
Here is a running example with your code:
const data = [
{
id: "1",
name: "Luke"
},
{
id: "2",
name: "Jim",
childNodes: [
{
id: "3",
name: "Lola"
}
]
}
];
class NestedList extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true
};
}
toggle = () => {
this.setState({ visible: !this.state.visible });
};
renderChild = child => {
if (child.childNodes) {
return (
<ul>
{child.childNodes.map(item => {
return this.renderChild(item);
})}
</ul>
);
} else if (child.name) {
return (
<div>
<input type="checkbox"/>
<Child name={child.name} />
</div>
);
}
return null;
};
render() {
return (
<aside>
<div>
<h4>Data Sets</h4>
<ul>{this.props.myData.map(item => this.renderChild(item))}</ul>
</div>
</aside>
);
}
}
class Child extends React.Component {
render() {
let { name } = this.props;
return <li>{name}</li>;
}
}
ReactDOM.render(<NestedList myData={data} />, document.getElementById("root"));
<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>
I try to write to make my own custom component but it doesn't work as I wish. The interaction seems ok but the it's only render the last item of the arrays.
export default class MyComponent extends Component {
constructor() {
super()
this.state = {
openItems: false,
selectedItem: 'Please select'
}
}
render() {
const { items, className } = this.props
const { openItems, selectedItem } = this.state
return (
<div className={classnames('myComponent', className)}>
<div tabIndex="1"
onBlur={() => this.setState({ openItems: false })}
onFocus={() => this.setState({ openItems: true })}>
{selectedItem}
<div className={classnames({'show': openItems === true, 'hide': openItems === false})}>
{items.map((obj, i) => {
return(
<li onClick={() => this.setState({ selectedItem: obj.name, openItems: false })}
key={i}>
{obj.name}
</li>
)
})}
</div>
</div>
</div>
)
}
}
and somewhere I used the component like this
<MyComponent items={[{
name: 'abc',
name: 'def',
name: 123
}]} />
I have no clue what the mistake is.
Your component expects an array of object with the key name. When you've initialized your component, you've only passed in a single object with the key name duplicated three times:
<MyComponent items={[{
name: 'abc',
name: 'def', // <-- this overrides the previous 'abc'
name: 123 // <-- this overrides the previous 'def'
}]} />
What you want is this:
<MyComponent items={[
{ name: 'abc' },
{ name: 'def' },
{ name: 123 },
]} />
You seem to be passing the same object name thrice, this will override the previous values and the latest value will take the final form.
Consider passing different values with different names.
<MyComponent items={[{ name: 'abc',
name: 'def',
name: 123
}]} />
pass the props into the constructor method as I am looking you have not passed the props so let's try to add props into the constructor as given below:-
constructor(props) {
super(props)
this.state = {
openItems: false,
selectedItem: 'Please select'
}
}
I have a parent component that holds a number of children components. I want to add an active className to a a child component when it is clicked on.
This is working, but the issue is that each child component can have an active classname. Only one of the components should have be active each time.
Does anyone have any idea how to solve this issue?
Please see my code below.
class MyComponent extends Component {
constructor(props) {
super(props);
this.addActiveClass= this.addActiveClass.bind(this);
this.state = {
active: false,
};
}
addActiveClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
};
render() {
return (
<div
className={this.state.active ? 'active': null}
onclick={this.addActiveClass}
>
<p>{this.props.text}</p>
</div>
)
}
}
class Test extends Component {
render() {
return (
<div>
<MyComponent text={'1'} />
<MyComponent text={'2'} />
<MyComponent text={'3'} />
<MyComponent text={'4'} />
</div>
);
}
}
Add the active functionality to your Test Component.
There you can check whether there is already active Component or not.
class MyComponent extends React.Component {
render() {
return <div
id={this.props.id}
className={this.props.active ? 'active': null}
onClick={this.props.handleClick} >
{this.props.text}
</div>
}
}
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
components: [
{id: 1, text: 1},
{id: 2, text: 2},
{id: 3, text: 3},
{id: 4, text: 4}
],
activeID: null
};
}
handleClick(e) {
// If there is already active component ID, don't set another one!
// We support only one active ID.
if (this.state.activeID !== null) return;
const id = parseInt(e.target.id);
this.setState({
activeID: id
});
}
renderComponents() {
return (this.state.components.map( c =>
<MyComponent
id={c.id}
active={c.id === this.state.activeID}
text={c.text}
handleClick={this.handleClick.bind(this)} /> ));
}
renderActiveIDText() {
return (this.state.activeID ? <p>{"Active Component ID: " + this.state.activeID}</p> : null );
}
render() {
return <div>
{this.renderActiveIDText()}
{this.renderComponents()}
</div>
}
}
ReactDOM.render(<Test />, document.getElementById('container'));
<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="container">
<!-- This element's contents will be replaced with your component. -->
</div>