How to access the data passed through "this.props.history.push(...)" - reactjs

Constructor code which contains the data
constructor(props){
super(props);
this.state = {
Data: [
{
"name": 'red'
},
{
"name": 'green'
},
{
"name": 'red'
},
{
"name": 'brown'
},
{
"name": 'yellow'
},
{
"name": 'brown'
}
]
}
}
The code for my button where I map the data
{this.state.Data.map((color) => (
<>
<div className="ViewDetailsBtn"><Button onClick={this.clickMe.bind(this, color.name)} className="ViewDetailsBtnLink">View Details</Button></div></>
))}
onClick function Code
clickMe = (details) => {
console.log(details);
this.props.history.push({
pathname: "/ViewDetails",
state: {detail: details}
});
}
Here it displays the color name on my console properly and it redirects me to ViewDetails but how do I display the color name on the ViewDetails page?
ViewDetails page code
import React from 'react'
const App = (props) => {
console.log(props);
//const data = props.location.state.detail;
return (
<div>
<h1>Details:-</h1>
{/* <h1>{data}</h1> */}
</div>
)
}
export default App

your ViewDetails component should accept a parameter:
const ViewDetails = (props) => {}
then you should be able to access that data through that parameter

Related

How to render component which store in object for Reactjs

I have few components import into this file. It will render it depending if match the roles.
As example, we have "A" role in this user. It should render AdminDashboard and CustomerDashboard. How to do in this way if I save details in DashboardComponents Object and try to render component if filter matched my condition.
I will pass into the props into the component too.
import AdminDashboard from "../AdminDashboard";
import ProjectDashboard from "../ProjectDashboard";
import CustomerDashboard from "../CustomerDashboard"
const DashboardComponents = [
{ id: 1, component: AdminDashboard, roles: [ "A" ] },
{ id: 2, component: ProjectDashboard, roles: [ "C", "D" ] },
{ id: 3, component: CustomerDashboard, roles: [ "A", "B" ] },
]
const DashboardComponent = () => {
/**
I tried use React.cloneElement for component Dashboard which from DashboardComponents.
it can't be render.
**/
return (
/** render the match DashboardComponents with props data={ data } in here **/
)
}
I knew we can do it in this way. but it look messy for me if I have 10 Dashboard.
import AdminDashboard from "../AdminDashboard";
import ProjectDashboard from "../ProjectDashboard";
import CustomerDashboard from "../CustomerDashboard"
const DashboardComponent = () => {
return (
<>
{
condition === "A" && (
<AdminDashboard data={ data } />
)
}
{
condition === "A" && (
<ProjectDashboard data={ data } />
)
}
{
condition === "A" && (
<CustomerDashboard data={ data } />
)
}
</>
)
}
You can try:
const AdminDashboard = ({data}) => {
return (
<div>
AdminDashboard: {data}
</div>
)
}
const ProjectDashboard = ({data}) => {
return (
<div>
ProjectDashboard: {data}
</div>
)
}
const CustomerDashboard = ({data}) => {
return (
<div>
CustomerDashboard: {data}
</div>
)
}
const DashboardComponents = [
{ id: 1, component: AdminDashboard, roles: [ "A" ] },
{ id: 2, component: ProjectDashboard, roles: [ "C", "D" ] },
{ id: 3, component: CustomerDashboard, roles: [ "A", "B" ] },
]
export default function App() {
const condition = 'A';
let dash_role = DashboardComponents.filter(dc => dc.roles.indexOf(condition) > -1);
return dash_role.map(
dr => {
const Comp = dr.component;
return (
<Comp
key={dr.id}
data={`data_props ${dr.id}`}
/>
)
}
)
}
And if you want to use React.cloneElement:
// add onPress for who you want
const ProjectDashboard = ({data, onPress}) => {
return (
<div>
ProjectDashboard: {data}
<button onClick={() => onPress(data)}>Click!</button>
</div>
)
}
export default function App() {
const condition = 'A';
let dash_role = DashboardComponents.filter(dc => dc.roles.indexOf(condition) > -1);
const onPress = data => {
alert(data);
}
return dash_role.map(
dr => {
const Comp = dr.component;
const child = (
<Comp
key={dr.id}
data={`data_props ${dr.id}`}
/>
);
return React.cloneElement(child, { onPress});
}
)
}
As your role "A" have 2 dashboards so I am supposing you only want to access one dashboard at time. So I have written code of it and you have to pass the role name in find function it will fetch the component for it.
import AdminDashboard from "../AdminDashboard";
import ProjectDashboard from "../ProjectDashboard";
import CustomerDashboard from "../CustomerDashboard"
const DashboardComponents = [
{ id: 1, component: AdminDashboard, roles: [ "A" ] },
{ id: 2, component: ProjectDashboard, roles: [ "C", "D" ] },
{ id: 3, component: CustomerDashboard, roles: [ "A", "B" ] },
]
const DashboardComponent = () => {
const find=(role)=>DashboardComponents.filter((item)=>item.roles.includes(role));
return (
<>
{find('A')?.[0]?.component(data)} {/* pass data as props to that component */}
</>
)
}

change text of a specific button when clicked in React

I want to change the text of a specific button when I click on that button in React. But the issue is when I click the button the title will change for all buttons!
class Results extends Component {
constructor() {
super();
this.state = {
title: "Add to watchlist"
}
}
changeTitle = () => {
this.setState({ title: "Added" });
};
render() {
return (
<div className='results'>
{
this.props.movies.map((movie, index) => {
return (
<div className='card wrapper' key={index}>
<button className='watchListButton' onClick={this.changeTitle}>{this.state.title}</button>
</div>
)
})
}
</div>
)
}
}
You would need to come up with a mechanism to track added/removed titles per movie. For that, you would have to set your state properly. Example:
this.state = {
movies: [
{id: 1, title: 'Casino', added: false},
{id: 2, title: 'Goodfellas', added: false}
]
This way you can track what's added and what's not by passing the movie id to the function that marks movies as Added/Removed. I have put together this basic Sandbox for you to get you going in the right direction:
https://codesandbox.io/s/keen-moon-9dct9?file=/src/App.js
And here is the code for future reference:
import React, { Component } from "react";
import "./styles.css";
class App extends Component {
constructor() {
super();
this.state = {
movies: [
{ id: 1, title: "Casino", added: false },
{ id: 2, title: "Goodfellas", added: false }
]
};
}
changeTitle = (id) => {
this.setState(
this.state.movies.map((item) => {
if (item.id === id) item.added = !item.added;
return item;
})
);
};
render() {
const { movies } = this.state;
return (
<div className="results">
{movies.map((movie, index) => {
return (
<div className="card wrapper" key={index}>
{movie.title}
<button
className="watchListButton"
onClick={() => this.changeTitle(movie.id)}
>
{movie.added ? "Remove" : "Add"}
</button>
</div>
);
})}
</div>
);
}
}
export default App;

How to map nested array in gatsby with reactjs and graphql

I have a component, menu.js, that i import into a page to produce a list of articles, that can be filtered by category. This works perfectly.
Now i want to change the component so that i can filter the articles by tags. The problem is that the tags are a nested array in graphql, that i cant reach with the same map() function that maps the categories.
I have tried to do a nested map function but i cant get it to work, but i suspect that is the solution. My goal is to have the same functionality where i can filter the articles by tags, instead of by category. I hope thats possible. I am using gatsby, with a Strapi backend. Any hints in the right direction appreciated :-)
/src/pages/articles.js
import graphql from 'gatsby'
import React from 'react'
import Layout from 'components/layout'
import MenuBlog from 'components/menublog'
const BlogPage = ({ data }) => (
<Layout>
<MenuBlog items={data.menu} />
</Layout>
)
export default BlogPage
export const pageQuery = graphql`
query BlogQuery {
menu: allStrapiArticle {
edges {
node {
id
title
slug
tag {
title
id
}
category {
title
id
}
}
}
}
}
`
This is what i get back from the GraphQL query above, each article can of course have one or more tags, but only one category assigned
{
"data": {
"menu": {
"edges": [
{
"node": {
"title": "articleName 1",
"slug": "articleName-1",
"category": {
"title": "cat1"
},
"tag": [
{
"title": "tag1"
},
{
"title": "tag2"
},
{
"title": "tag3"
}
]
}
},
{
"node": {
"title": "articleName 2",
"slug": "articleName-2",
"category": {
"title": "cat2"
},
"tag": [
{
"title": "tag3"
}
]
}
}
]
}
}
}
And here is my component that displays the articles according to the chosen category
/src/components/menublog/index.js
import React, { Component } from 'react'
import { Link } from 'gatsby'
import Row from 'react-bootstrap/Row'
const getCategories = items => {
let tempItems = items.map(items => {
return items.node.category.title
})
let tempCategories = new Set(tempItems)
let categories = Array.from(tempCategories)
categories = ['all', ...categories]
return categories
}
export default class MenuBlog extends Component {
constructor(props) {
super(props)
this.state = {
items: props.items.edges,
articles: props.items.edges,
categories: getCategories(props.items.edges),
}
}
handleItems = category => {
let tempItems = [...this.state.items]
if (category === 'all') {
this.setState(() => {
return { articles: tempItems }
})
} else {
let items = tempItems.filter(
({ node }) => node.category.title === category
)
this.setState(() => {
return { articles: items }
})
}
}
render() {
if (this.state.items.length > 0) {
return (
<Row>
{/* items */}
<div className="col-md-8 blog-main bg-light">
<h1>Artikler</h1>
{this.state.articles.map(({ node }) => {
return (
<div key={node.id} className="blog-post mb-4">
<h2>
<Link to={`/artikler/${node.slug}`}>{node.title}</Link>
</h2>
{/* item text */}
</div>
)
})}
</div>
{/* categories */}
<div className="col-md-4 blog-sidebar">
<div className="p-4 mb-3 bg-light">
<h4>Kategorier</h4>
<ol className="list-unstyled mb-0">
{this.state.categories.map((category, index) => {
return (
<li key={index}>
<button
type="button"
className="btn"
onClick={() => {
this.handleItems(category)
}}
>
{category}
</button>
</li>
)
})}
</ol>
</div>
<div className="p-4 mb-3 bg-light">
<h4>Kategorier</h4>
</div>
</div>
</Row>
)
} else {
return <h1>no items</h1>
}
}
}
You should be able to use something similar to your category method:
items = tempItems.filter(({ node }) =>
node.tag.map(tag => tag.title).includes("tag2")
);
Since this isn't necessarily React / Gatsby specific, here is only the data and these methods:
const data = {
data: {
menu: {
edges: [{
node: {
title: "articleName 1",
slug: "articleName-1",
category: {
title: "cat1"
},
tag: [{
title: "tag1"
},
{
title: "tag2"
},
{
title: "tag3"
}
]
}
},
{
node: {
title: "articleName 2",
slug: "articleName-2",
category: {
title: "cat2"
},
tag: [{
title: "tag3"
}]
}
}
]
}
}
};
let items = data.data.menu.edges.filter(
({
node
}) => node.category.title === "cat2"
);
console.log(items);
items = data.data.menu.edges.filter(({
node
}) =>
node.tag.map(tag => tag.title).includes("tag2")
);
console.log(items);

Displaying Data from One Component in Another

I'm learning react at the moment and I'm trying to have two components interact with each other. The hierarchy is as follows:
App
--SearchForm
--Results
There's a data object that will be filtered through a string I enter in the SearchForm component. The filtered result should be displayed in the Results component.
My logic was to have all the functions needed in the App component, and pass the data to the individual components.
I want to be able to display the filtered data in the results component.
Can anyone help me with this please?
Please find the App.js file's code below, as well as a sample of the object I'm using.
App.js
import React, { Component } from "react";
import styled from "styled-components";
import Header from "./Header";
import SearchForm from "./SearchForm";
import Results from "./Results";
import Map from "./Map";
const Outer = styled.div`
text-align:center;
`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
query: "",
data: [],
refinedData: [],
};
// this.handleSearchChange = this.handleSearchChange.bind(this);
}
handleSearchChange = (event) => {
this.setState({
query: event.target.value,
});
}
getData = async () => {
const response = await fetch("http://localhost:4200/bookings");
const json = await response.json();
this.setState({
data: json,
})
console.log(this.state.data);
}
filterData = () => {
const filtered = this.state.data.filter(element => {
return element.toLowerCase().includes(this.state.query.toLowerCase());
});
this.setState({
refinedData: filtered,
});
console.log(this.state.refinedData);
}
componentDidMount() {
this.getData();
}
render() {
return (
<Outer>
<Header/>
<SearchForm triggeredUpdate={this.handleSearchChange}/>
<Results searchQuery={this.state.filterData}/>
<Map/>
</Outer>
);
}
}
export default App;
Object
[
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
]
This is a simple logic actually in React. You want to show filtered results in your Results component, then you pass the filtered state to it. You can trigger the search with a button, then maybe the suitable place for this can be Search component. For this, you will pass your filterData method to it as a prop as you think.
I said a few times "it is an array not object" in my comments since the last data you show in your question says Object as bold but it is an array :) So, I got confused but you are doing it right.
You should filter your data with a prop in your object. As you think again, like user.name, car.license_late etc. You need a target here.
Here is a simple working example:
class App extends React.Component {
state = {
query: "",
data: [
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
],
refinedData: [],
};
handleSearchChange = event => this.setState({
query: event.target.value,
});
filterData = () => {
const { data, query } = this.state;
const filtered = !query ? [] : data.filter(element =>
element.car.licence_plate.toLowerCase().includes(this.state.query.toLowerCase())
);
this.setState({
refinedData: filtered,
});
}
render() {
return (
<div>
<SearchForm filterData={this.filterData} triggeredUpdate={this.handleSearchChange} />
<Results refinedData={this.state.refinedData} />
</div>
);
}
}
const Results = props => (
<div>
{
props.refinedData.map( el =>
<div key={el.id}>
<p>ID: {el.id}</p>
<p>User name: {el.user.name}</p>
</div>
)
}
</div>
)
const SearchForm = props => (
<div>
<input onChange={props.triggeredUpdate} />
<br />
<button onClick={props.filterData}>Search</button>
</div>
)
ReactDOM.render(<App />, 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>
Update after discussion on chat
You can do the search without a button while you typing. We don't have filterData method anymore since we moved the filter logic into handleSearchChange method. Also, we don't need any query state right now.
filterData array created with a ternary operator. If there is no search value we are returning an empty array since we don't want to list all of our data if there is not any search. By the way, I've updated my previous solution according to that, too. It was returning all the data if we hit the Search button with an empty input.
class App extends React.Component {
state = {
data: [
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
],
refinedData: [],
};
handleSearchChange = event => {
const { value: query } = event.target;
this.setState(prevState => {
const filteredData = !query ? [] : prevState.data.filter(element =>
element.car.licence_plate.toLowerCase().includes(query.toLowerCase())
);
return {
refinedData: filteredData
};
});
}
render() {
return (
<div>
<SearchForm triggeredUpdate={this.handleSearchChange} />
<Results refinedData={this.state.refinedData} />
</div>
);
}
}
const Results = props => (
<div>
{
props.refinedData.map(el =>
<div key={el.id}>
<p>ID: {el.id}</p>
<p>User name: {el.user.name}</p>
</div>
)
}
</div>
)
const SearchForm = props => (
<div>
<input onChange={props.triggeredUpdate} />
</div>
)
ReactDOM.render(<App />, 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>

Uncaught TypeError - issue mapping over json object in React

I have a react component which is pulling in user data, and should be able to display one of the values from the JSON object. However, I'm getting the following Uncaught TypeError: this.state.reasons.map is not a function error. I believe its because it is expecting an array vs an object, but not quite sure how to check or convert as needed. It should be able to map over the object and render the value of subscription.current_period_end
This is the JSON object:
{
"displayName": "username",
"email": "user#email.com",
"entitled": true,
"id": "23456789",
"subscription": {
"canceled_at": 1508519952,
"current_period_end": 1524765490,
"is_premium": true,
"is_renewing": false,
"plan_type": "Annual",
"saved": true,
"status": "Active"
},
"country": "us",
"language": "en"
}
React Component
class CancelConfirm extends React.Component {
constructor(props) {
super(props)
this.state = {
reasons: []
}
this.processData = this.processData.bind(this)
}
componentDidMount() {
this.fetchContent(this.processData)
}
fetchContent(cb) {
superagent
.get('/api/user')
.then(cb)
}
processData(data) {
this.setState({
reasons: data.body
})
}
render(props) {
const content = this.props.config.contentStrings
const reason = this.state.reasons.map((reason, i) => {
return (
<p key={i}>{reason.subscription.current_period_end}</p>
)
})
console.log(reason)
return (
<div className = 'confirm' >
<p className = 'confirm-subpara' >{reason}</p>
</div>
)
}
}
export default CancelConfirm
Your this.state.reasons is response object from JSON, object doesn't have map function it's only for iterator values such as new Array() if you are trying to show reason you would simply do
Currently I don't know why you are trying to iterate over object when it has no array array data, maybe I got your question wrong so please specify why you even did map instead of simple object reference.
var API_RESPONSE = {
"body": {
"displayName": "username",
"email": "user#email.com",
"entitled": true,
"id": "23456789",
"subscription": {
"canceled_at": 1508519952,
"current_period_end": 1524765490,
"is_premium": true,
"is_renewing": false,
"plan_type": "Annual",
"saved": true,
"status": "Active"
},
"country": "us",
"language": "en"
}
}
class CancelConfirm extends React.Component {
constructor(props) {
super(props)
this.state = {
reasons: false
}
this.processData = this.processData.bind(this)
}
componentDidMount() {
this.fetchContent(this.processData)
}
fetchContent(cb) {
setTimeout(() => {
cb(API_RESPONSE)
}, 2000)
}
processData(data) {
this.setState({
reasons: data.body
})
}
render(props) {
if (!this.state.reasons) return (<i>Loading ....</i>)
const reason = <p>{this.state.reasons.subscription.current_period_end}</p>
return (
<div className = 'confirm' >
<p className = 'confirm-subpara' >{reason}</p>
</div>
)
}
}
ReactDOM.render(<CancelConfirm />, document.getElementById('react'))
<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="react"></div>
You can't map() over an Object. As such it would be better to do something like this:
render(props) {
const content = this.props.config.contentStrings
const { reasons } = this.state;
const keys = Object.keys(reasons);
return (
keys.map((k, i) => {
return <p key={i}>{reasons[k].subscription.current_period_end}</p>
})
)
}

Resources