Can't update props of child components generated from JSON - json

I just started learing react and I run in a trouble trying to update state of a single <Option /> child Element.
My flux Store is emiting change and in React devtools I can see the state of StyleOptions element being updated but it doesn't update the child components <Option />.
I suspect this is because I got the list of options kept in a variable.
I need to use this because I'm pulling this options from JSON.
const Options = this.state.options.map((parent) => {
const children = parent.children.map((child) => {
return (
<Option {...child} />
)
});
return <Option {...parent} children={children} />;
});
So I think this part might be causing problems.
My example data from OptionsStore looks like this.
this.options = [
{
key: "suitType",
label: "Suit Type",
selected: false,
children: [
{
key: "suittype_skinny",
parent: "suitType",
label: "Skinny",
price: "£50",
description: "Short description",
images: {
general: "http://placehold.it/600x600",
closeUp: "http://placehold.it/620x620",
thumbnail: "http://placehold.it/100x100",
},
selected: false,
},
{
key: "suittype_wedding",
parent: "suitType",
label: "Wedding",
price: "£50",
description: "Short description",
images: {
general: "http://placehold.it/600x600",
closeUp: "http://placehold.it/620x620",
thumbnail: "http://placehold.it/100x100",
},
selected: false,
}
]
}
]
Also the child props aren't being changed.
Full code here:
import React, { Component } from 'react';
import Option from './Option';
import OptionsStore from '../../stores/OptionsStore';
class StyleOptions extends Component {
constructor(props) {
super(props)
this.state = {
options: OptionsStore.getAllItems(),
}
}
componentDidMount() {
OptionsStore.on('change',(e) => {
this.setState({
options: OptionsStore.getAllItems(),
});
console.log('optionsStore received an update');
});
}
render() {
const Options = this.state.options.map((parent) => {
const children = parent.children.map((child) => {
return (
<Option {...child} />
)
});
return <Option {...parent} children={children} />;
});
return(
<div className="col-xs-6">
<ul className="list-group">
{Options}
</ul>
</div>
)
}
}
export default StyleOptions;
also the <Option /> code:
import React, { Component } from 'react';
export default class Option extends Component {
constructor(props) {
super(props);
this.hasChildren = this.props.children ? true : false;
this.hasThumb = this.props.images ? true : false;
this.children = this.state.children;
this.state = {
label: this.props.label,
description: this.props.description,
selected: false,
price: this.props.price
}
}
render() {
return (
<li className={this.hasChildren ? 'list-group-item':'col-sm-4 list-group-item' } selected={this.state.selected}>
<a className="media">
{this.hasThumb ? (
<div className="media-left media-middle">
<img src={this.props.images.thumbnail} alt={this.state.label} />
</div>
) : (
' '
)}
<div className="media-body">
<h4 className="option-name">{this.state.label}</h4>
<p className="info">{this.state.description}</p>
<span className="text-success pricing">{this.state.price}</span>
</div>
</a>
{this.hasChildren ? (
<ul className="panel-body">
{this.children}
</ul>
) : (
' '
)}
</li>
)
}
}
I hope anyone could help.

The issue is inside of your Option component.
You define this.children = this.state.children . After that, you define your initial state but there is no "children". So that children state is notdefined.
First, add children: this.props.children into your state.
Then, change
{this.hasChildren ? (
<ul className="panel-body">
{this.children}
</ul>
) : (
' '
)}
to
{this.hasChildren ? (
<ul className="panel-body">
{this.state.children}
</ul>
) : (
' '
)}
and there is no need to define this.children = this.state.children.
I hope it solves the issue.

Thank you alireza for your help.
I managed to fix it. The problem was that the <Option /> was receiving too much info. I removed all state calls and left only the if statements like below.
import React, { Component } from 'react';
export default class Option extends Component {
constructor(props) {
super(props);
this.hasChildren = this.props.children ? true : false;
this.hasThumb = this.props.images ? true : false;
//this.state = this.props;
}
render() {
return (
<li className={this.hasChildren ? 'list-group-item':'col-sm-4 list-group-item' }>
<a className="media">
{this.hasThumb ? (
<div className="media-left media-middle">
<img src={this.props.images.thumbnail} alt={this.props.label} />
</div>
) : (
' '
)}
<div className="media-body">
<h4 className="option-name">{this.props.label}</h4>
<p className="info">{this.props.description}</p>
<span className="text-success pricing">{this.props.price}</span>
</div>
</a>
{this.hasChildren ? (
<ul className="panel-body">
{this.props.children}
</ul>
) : (
' '
)}
</li>
)
}
}
Then modified my stateful component <StyleOptions /> like below
import React, { Component } from 'react';
import Option from './Option';
import OptionsStore from '../../stores/OptionsStore';
class StyleOptions extends Component {
constructor(props) {
super(props)
this.state = {
options: OptionsStore.getAllItems(),
}
}
componentWillMount() {
OptionsStore.on("change", () => {
this.setState({
options: OptionsStore.getAllItems(),
});
console.log('optionsStore received an update');
});
}
render() {
const { options } = this.state;
const allOptions = options.map((option) => {
const { children } = option;
const optionChildren = children.map((child) => {
return <Option {...child} />;
})
return <Option {...option} children={optionChildren} />;
});
return(
<div className="col-xs-12">
<ul className="list-group">
{allOptions}
</ul>
</div>
)
}
}
export default StyleOptions;
Not sure why it is working correctly now. I suspect that It might have changed because I modified the maps a little bit.
Old one / Broken one:
const Options = this.state.options.map((parent) => {
const children = parent.children.map((child) => {
return (
<Option {...child} />
)
});
return <Option {...parent} children={children} />;
});
New one/working:
const { options } = this.state;
const allOptions = options.map((option) => {
const { children } = option;
const optionChildren = children.map((child) => {
return <Option {...child} />;
})
return <Option {...option} children={optionChildren} />;
});

Related

How i can do my component in React.js have a individual behavior?

I'm implementing a Like and Dislike Button, and I wanna that when I click them will be with other colors, but just the clicked component, when I click all buttons change the state, can anybody help me?
`
const indexPost = async () => {
const data = await api.get('/api/posts')
if(data.data.length !=0){
const dataArray = data.data
if(dataArray.length === 0) {
return
}else{
return(
setPost(dataArray.map( data => (
<Post key={data._id} id={data._id} title={data.title} text={data.text}>
<Like id={data._id}></Like>
</Post>
)))
)
}
}
}
export default function Like({itemId}) {
const context = useContext(notificationContext)
const {isLoved, Like, Loved, Unlike, isLike, isUnlike, setIsLike, setIsUnlike, setIsLoved } = context
return(
<div className={styles.likeContainer} key={itemId}>
{isLike ? (
<button className={styles.likeContent} onClick={() => setIsLike(false)}><Icon.ThumbsUp className={styles.Icon} fill="#5CB0BB" ></Icon.ThumbsUp></button>) :
(<button className={styles.likeContent} onClick={() => Like() }><Icon.ThumbsUp className={styles.Icon} ></Icon.ThumbsUp></button>)}
{isLoved ?
(<button className={styles.likeContent} onClick={() => setIsLoved(false)}><Icon.Heart className={styles.Icon} fill="red" ></Icon.Heart> </button>) :
(<button className={styles.likeContent} onClick={() => Loved() }><Icon.Heart className={styles.Icon} ></Icon.Heart></button>)}
{isUnlike ? (
<button className={styles.likeContent} onClick={() => setIsUnlike(false)}><Icon.ThumbsDown className={styles.Icon} fill="#702BA6" ></Icon.ThumbsDown> </button>) :
(<button className={styles.likeContent} onClick={() => Unlike()}><Icon.ThumbsDown className={styles.Icon} ></Icon.ThumbsDown></button>
)}
</div>
)
};
I have implemented the similar one in my project, it is very basic , it shows how to update the likes , you need to handle the cases of user authentication and stuff
App.js
import { useState, useEffect, createContext, useReducer } from "react";
import { updateArrayOfObj } from "./utils";
import AllPosts from "./AllPosts";
export const PostsContext = createContext();
const initialState = {
posts: [
{
_id: "1",
name: "Browny",
image: "http://placekitten.com/200/310",
likes: 0,
love: 0,
dislikes: 0
},
{
_id: "2",
name: "Blacky",
image: "http://placekitten.com/200/320",
likes: 0,
love: 0,
dislikes: 0
},
{
_id: "3",
name: "SnowWhite",
image: "http://placekitten.com/200/300",
likes: 0,
love: 0,
dislikes: 0
}
]
};
const reducer = (state, action) => {
switch (action.type) {
case "UPDATE_POST":
return {
...state,
posts: updateArrayOfObj(
state.posts,
action.payload.obj,
"_id",
action.payload._id
)
};
case "CREATE_POST":
return {
...state,
posts: [...state.posts, ...action.payload.data]
};
case "DELETE_POST":
return {
...state,
posts: state.posts.filter((ele) => ele._id !== action.payload._id)
};
default:
return state;
}
};
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<PostsContext.Provider
value={{
state,
dispatch
}}
>
<div className="App">
<AllPosts />
</div>
</PostsContext.Provider>
);
}
PostsAll.js
import Post from "./Post";
import { PostsContext } from "./App";
import { useContext } from "react";
export default function AllPosts() {
const { state } = useContext(PostsContext);
return (
<div className="allPosts">
{state.posts.map((item) => {
return (
<Post
name={item.name}
image={item.image}
likes={item.likes}
love={item.love}
dislikes={item.dislikes}
id={item._id}
key={item._id}
/>
);
})}
</div>
);
}
Post.js
import { PostsContext } from "./App";
import { useContext } from "react";
export default function Post(props) {
const { state, dispatch } = useContext(PostsContext);
const handleUserInteraction = (type, id) => {
dispatch({
type: "UPDATE_POST",
payload: {
obj: { [type]: props[type] + 1 },
_id: id
}
});
};
return (
<div className="post">
<h3>{props.name}</h3>
<img src={props.image} alt="cat" />
<br />
<button onClick={() => handleUserInteraction("likes", props.id)}>
{props.likes} Like
</button>{" "}
<button onClick={() => handleUserInteraction("love", props.id)}>
{props.love} Love
</button>{" "}
<button onClick={() => handleUserInteraction("dislikes", props.id)}>
{props.dislikes} Dislike
</button>
</div>
);
}
You can refer to this codesandbox to implement the same
You can use onClick() on each like button and attach it with a function, then you can get the value of that particular like with e.currentTarget.id and change its css/style the way you want.
const handleClick=(e)=>
{
console.log(e.currentTarget.id);
}

How can i redirect to full post after click on title

I did data display from json with titles from all posts and now i need to do redirect to page with title and body after click on title, any ideas? Thanks for help
import "./style.css";
class Card extends React.Component {
state = {
loading: true,
posts: [],
};
async componentDidMount() {
const url = "https://jsonplaceholder.typicode.com/posts";
const response = await fetch(url);
const posts = await response.json();
this.setState({ posts, loading: false });
}
render() {
return (
<div>
{this.state.loading || !this.state.posts.length === 0 ? (
<div> loading...</div>
) : (
<div>
{this.state.posts.map((post) => (
<div key={post.id} className="post-header">
{post.title}
</div>
))}
</div>
)}
</div>
);
}
}
export default Card;```
I would recommend you to use Functional component as it would make your code short and much more readable.
But as per your code you can simply get the desired results by the making the following changes in your code.
import { useHistory } from 'react-router-dom';
class Card extends React.Component {
const history = useHistory();
render() {
return (
<div>
{this.state.loading || !this.state.posts.length === 0 ? (
<div> loading...</div>
) : (
<div>
{this.state.posts.map((post) => (
<div key={post.id} className="post-header">
<div onClick={() => history.push(post.title)}>
{post.title}
</div>
</div>
))}
</div>
)}
</div>
);
}
}
import "./style.css";
class Card extends React.Component {
state = {
loading: true,
posts: [],
};
async componentDidMount() {
const url = "https://jsonplaceholder.typicode.com/posts";
const response = await fetch(url);
const posts = await response.json();
this.setState({ posts, loading: false });
}
gotoPage=(data)=>()=>{ //write this function above render
this.props.history.push('/'+data);
}
render() {
return (
<div>
{this.state.loading || !this.state.posts.length === 0 ? (
<div> loading...</div>
) : (
<div>
{this.state.posts.map((post) => (
<div key={post.id} className="post-header">
<span onClick={this.gotoPage(post.title)}>{post.title}</span> // You can use this function, when user user click on title, navigate page to that title
</div>
))}
</div>
)}
</div>
);
}
}
export default Card;```

react native 2 dropdown depend on first one

I am trying to connect two dropdown with in react native,
same as country and city
if i select any country it should load cities from that country to second drop-down
all data is in a external json file
but nothing is loading in both drop down (picker)
json file :
{
"interest": [
{
"RAW_MATERIAL":["abc","cde"]
},
{
"OEM_PARTS":["xyz","qwer"]
},
{
"CONSUMABLES":["poiu","fjgl"]
},
{
"SERVICE":["xvcbv","qweiw"]
}
],
}
react native picker i use:
import React, { Component } from 'react';
import { Container,Picker,Button } from 'native-base';
const cData = require('../data.json');
export default class Vendorsupplies extends Component {
constructor(props) {
super(props);
this.state = {
interest:'',
interest2:''
};
}
interest(value: string) {
this.setState({
interest: value
});
}
interest2(value: string) {
this.setState({
interest2: value
});
}
<Picker
note
mode="dropdown"
style={{ width: 120 }}
selectedValue={this.state.interest}
onValueChange={this.interest.bind(this)}
name="intre"
>
{cData.interest.map((number) =>
<Picker.Item label={number.interest_in} value={number.interest_in} />
)}
</Picker>
<Picker
note
mode="dropdown"
style={{ width: 120 }}
selectedValue={this.state.intre2.interest}
onValueChange={this.intre2.interest.bind(this)}
name="intre2"
>
{cData.interest.map((number) =>
<Picker.Item label={number.intre2.interest_in} value={number.intre2.interest_in} />
)}
</Picker>
You can try it like that, I just done it with select but you will get the idea.
const cData = {
interest: [
{
RAW_MATERIAL: ['abc', 'cde'],
},
{
OEM_PARTS: ['xyz', 'qwer'],
},
{
CONSUMABLES: ['poiu', 'fjgl'],
},
{
SERVICE: ['xvcbv', 'qweiw'],
},
],
};
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
interest: 'RAW_MATERIAL',
interest2: '',
};
}
renderOption() {
const el = cData.interest.find(
interest => Object.keys(interest)[0] === this.state.interest
);
if (el) {
return el[this.state.interest].map(option => (
<option value={option}>{option}</option>
));
}
return <option>empty</option>;
}
render() {
return (
<React.Fragment>
<select
value={this.state.interest}
onChange={e => {
e.persist();
this.setState(prev => ({
...prev,
interest: e.target.value,
}));
}}
>
{cData.interest.map(el => (
<option value={Object.keys(el)}>{Object.keys(el)}</option>
))}
</select>
<select
value={this.state.interest2}
onChange={e => {
e.persist();
this.setState(prev => ({ ...prev, interest2: e.target.value }));
}}
>
{this.renderOption()}
</select>
</React.Fragment>
);
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
<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="app"></div>

My dialog box component is not Showing in Reactjs

I have a component name dialog Box and i want to show it on click over all other components . I have a main component as
class ImageEditor extends Component {
constructor() {
super();
this.state = { images: [], redirect: 'no', imageId: '', isDialogOpen: 'no' };
}
componentDidMount() {
let promise = apiGateway.getImages();
promise.then((images) => {
this.setState({ images: images });
});
}
openDialog = () =>{
this.setState({ isDialogOpen : 'yes' });
}
closeDialog = () =>{
this.setState({ isDialogOpen: 'no' });
}
deleteImage = (id) => {
apiGateway.removeImage(id);
}
setRedirect = (id) => {
this.setState({ redirect: 'yes', imageId: id });
}
renderImage(image,index){
return(
<div key={index}>
<p>Title: {image.title}</p>
<p>Description: {image.description}</p>
<button onClick={(e)=> this.deleteImage(image._id)}>DELETE</button>
<button onClick={(e)=> this.setRedirect(image._id)}>UPDATE</button>
<button onClick={this.openDialog}>SHARE</button>
<img className='image' src={image.link} width='100' height='100' alt='nature'/>
<br/><br/>
</div>
);
}
render() {
const { redirect , isDialogOpen } = this.state;
if(redirect === 'yes'){
return <Redirect to={`/update/${this.state.imageId}`}/>
}
if(isDialogOpen === 'yes'){
<DialogBox /> ????????
}
return(
<div>
<div>{
this.state.images.map((image,index)=>{
return this.renderImage(image,index);
})
}
</div>
</div>
);
}
}
export default ImageEditor;
When a person clicks on SHARE , I want Dialog Box to Open.I donot know the way i am doing i.e loading component with dialogBox is right or wrong. So every Advice or any suggestion will definitely help me. Here is my dialogue component(Right now i am getting a error as :Expected an assignment or function call and instead saw an expression no-unused-expressions)
class DialogBox extends Component {
render() {
return(
<div>
<Dialog title="Dialog Title" modal={true} onClose={this.handleClose} buttons={ [{ text: "Close", onClick: () => this.handleClose() }] }>
<h1>Dialog Content</h1>
<p>More Content. Anything goes here</p>
</Dialog>
</div>
);
}
}
You can do this:
return (
<div>
<insert your other components>
{this.state.isDialogOpen === 'yes' && <DialogBox />}
<div>
)
I'd advise using boolean instead of string for isDialogOpen.

React setState JSON.parse(result) [duplicate]

could you please tell me how to render a list in react js.
I do like this
https://plnkr.co/edit/X9Ov5roJtTSk9YhqYUdp?p=preview
class First extends React.Component {
constructor (props){
super(props);
}
render() {
const data =[{"name":"test1"},{"name":"test2"}];
const listItems = data.map((d) => <li key={d.name}>{d.name}</li>;
return (
<div>
hello
</div>
);
}
}
You can do it in two ways:
First:
render() {
const data =[{"name":"test1"},{"name":"test2"}];
const listItems = data.map((d) => <li key={d.name}>{d.name}</li>);
return (
<div>
{listItems }
</div>
);
}
Second: Directly write the map function in the return
render() {
const data =[{"name":"test1"},{"name":"test2"}];
return (
<div>
{data.map(function(d, idx){
return (<li key={idx}>{d.name}</li>)
})}
</div>
);
}
https://facebook.github.io/react/docs/jsx-in-depth.html#javascript-expressions
You can pass any JavaScript expression as children, by enclosing it within {}. For example, these expressions are equivalent:
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
This is often useful for rendering a list of JSX expressions of arbitrary length. For example, this renders an HTML list:
function Item(props) {
return <li>{props.message}</li>;
}
function TodoList() {
const todos = ['finish doc', 'submit pr', 'nag dan to review'];
return (
<ul>
{todos.map((message) => <Item key={message} message={message} />)}
</ul>
);
}
class First extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [{name: 'bob'}, {name: 'chris'}],
};
}
render() {
return (
<ul>
{this.state.data.map(d => <li key={d.name}>{d.name}</li>)}
</ul>
);
}
}
ReactDOM.render(
<First />,
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>
Shubham's answer explains very well. This answer is addition to it as per to avoid some pitfalls and refactoring to a more readable syntax
Pitfall : There is common misconception in rendering array of objects especially if there is an update or delete action performed on data. Use case would be like deleting an item from table row. Sometimes when row which is expected to be deleted, does not get deleted and instead other row gets deleted.
To avoid this, use key prop in root element which is looped over in JSX tree of .map(). Also adding React's Fragment will avoid adding another element in between of ul and li when rendered via calling method.
state = {
userData: [
{ id: '1', name: 'Joe', user_type: 'Developer' },
{ id: '2', name: 'Hill', user_type: 'Designer' }
]
};
deleteUser = id => {
// delete operation to remove item
};
renderItems = () => {
const data = this.state.userData;
const mapRows = data.map((item, index) => (
<Fragment key={item.id}>
<li>
{/* Passing unique value to 'key' prop, eases process for virtual DOM to remove specific element and update HTML tree */}
<span>Name : {item.name}</span>
<span>User Type: {item.user_type}</span>
<button onClick={() => this.deleteUser(item.id)}>
Delete User
</button>
</li>
</Fragment>
));
return mapRows;
};
render() {
return <ul>{this.renderItems()}</ul>;
}
Important : Decision to use which value should we pass to key prop also matters as common way is to use index parameter provided by .map().
TLDR; But there's a drawback to it and avoid it as much as possible and use any unique id from data which is being iterated such as item.id. There's a good article on this - https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
Try this below code in app.js file, easy to understand
function List({}) {
var nameList = [
{ id: "01", firstname: "Rahul", lastname: "Gulati" },
{ id: "02", firstname: "Ronak", lastname: "Gupta" },
{ id: "03", firstname: "Vaishali", lastname: "Kohli" },
{ id: "04", firstname: "Peter", lastname: "Sharma" }
];
const itemList = nameList.map((item) => (
<li>
{item.firstname} {item.lastname}
</li>
));
return (
<div>
<ol style={{ listStyleType: "none" }}>{itemList}</ol>
</div>
);
}
export default function App() {
return (
<div className="App">
<List />
</div>
);
}
import React from 'react';
class RentalHome extends React.Component{
constructor(){
super();
this.state = {
rentals:[{
_id: 1,
title: "Nice Shahghouse Biryani",
city: "Hyderabad",
category: "condo",
image: "http://via.placeholder.com/350x250",
numOfRooms: 4,
shared: true,
description: "Very nice apartment in center of the city.",
dailyPrice: 43
},
{
_id: 2,
title: "Modern apartment in center",
city: "Bangalore",
category: "apartment",
image: "http://via.placeholder.com/350x250",
numOfRooms: 1,
shared: false,
description: "Very nice apartment in center of the city.",
dailyPrice: 11
},
{
_id: 3,
title: "Old house in nature",
city: "Patna",
category: "house",
image: "http://via.placeholder.com/350x250",
numOfRooms: 5,
shared: true,
description: "Very nice apartment in center of the city.",
dailyPrice: 23
}]
}
}
render(){
const {rentals} = this.state;
return(
<div className="card-list">
<div className="container">
<h1 className="page-title">Your Home All Around the World</h1>
<div className="row">
{
rentals.map((rental)=>{
return(
<div key={rental._id} className="col-md-3">
<div className="card bwm-card">
<img
className="card-img-top"
src={rental.image}
alt={rental.title} />
<div className="card-body">
<h6 className="card-subtitle mb-0 text-muted">
{rental.shared} {rental.category} {rental.city}
</h6>
<h5 className="card-title big-font">
{rental.title}
</h5>
<p className="card-text">
${rental.dailyPrice} per Night · Free Cancelation
</p>
</div>
</div>
</div>
)
})
}
</div>
</div>
</div>
)
}
}
export default RentalHome;
Try this:
class First extends React.Component {
constructor (props){
super(props);
}
render() {
const data =[{"name":"test1"},{"name":"test2"}];
const listItems = data.map((d) => <li key={d.name}>{d.name}</li>;
return (
<div>
{listItems}
</div>
);
}
}