Rendering a Table of Data in React from JavaScript Object - javascript

I am trying to create a React component that renders a table of data from a JavaScript object pixarMovies that I initially set the state of. I would like to render a table of the movie data with movies sorted chronologically by date (I've attached an image of what I'm trying to do that I created in HTML). How might one accomplish this?
This component is separate from App.js, and I will include it in a different component. I would like the table to adjust accordingly to the state (to account for the addition and removal of movies), as well as be able to apply onClick functions to the buttons.
import React, { Component } from 'react';
import { BrowserRouter as Router, Redirect, Switch, Route, Link } from 'react-router-dom';
export default class PixarMovies extends React.Component {
constructor(props) {
super(props);
this.state = {
pixarMovies: [
{
title: "Cars",
date: "2006-06-09",
budget: "$120 million",
},
{
title: "Toy Story",
date: "1995-11-05",
budget: "$30 million",
},
{
title: "Ratatouille",
date: "2007-06-29",
budget: "$150 million",
},
{
title: "The Incredibles",
date: "2004-11-05",
budget: "$92 million"
}
]
}
}
render() {
return (
<div className="container">
<h1>Pixar Movies</h1>
{/* Insert Table here */}
</div>
);
}
}
Thanks!

OK, looks like there are a couple of pieces here, so let's take a look at each of them.
Sorting chronologically by date: Javascript arrays have a sort function that looks like this: myArray.sort(). You can pass a function to the .sort call to use whatever field you want. So maybe something like this for you:
const sortedPixarMovies = this.state.pixarMovies.sort((movA, movB) => new Date(movA.date) - new Date(movB.date));
Rendering a table: There are a bunch of ways to render a table in React. I'd recommend looking at some 3rd party components that will make rendering a table quite simple and effortless. If you want to build one yourself, take a look at something like this guide. A quick google of "render a table in react" should give you all the information you need.
Table should adjust according to state: This should happen automatically. Every time your component's state changes, your component will re-render, so it will re-render the table with the new data.
Apply onClick functions to buttons: I don't see any buttons in your code, but onClick functions work in react pretty much like they would in anything else. Define a function on your component, then pass it to the button. For example:
The function:
myOnChangeFunc = (newValue) => {
this.setState({ myField: newValue });
}
And then the button:
<button onChange={ this.myOnChangeFunc }>My Button</button>
Responding to question below
If you're using a 3rd party component, usually you want to pass your data in as a prop. So your render function might look something like:
render() {
const myFormattedData = this.state.data.map... (formatted);
return <ThirdPartyComponent data = { myFormattedData } />;
}
If you're doing it yourself, you can map the data in your render function. Here's a super simple example of how you would render a list:
render() {
return (
<ul>
{ this.state.listData.map((d) => <li key={ d.id }>{ d.value }</li>) }
</ul>
);
}

Related

props is undefined although state has changed

I am new in react. I have a parent component Navbar, that has state "Total_Item". This array is added on click of Modal. The values are populating. Now i want to get the length of this array and show on my cart button(presently a simple button), in my Navv component. But it says undefined.
So the data is not saved in props (Tot_Item ) in the Navbar component. I am sure there is some conceptual error how react renders. A clear explanation will really help at this point.
Please see the sandbox below:
https://codesandbox.io/s/blazing-sky-cet22
Thanks
sal
In file Navbar.jsx, value of this.state.Tot_Item is empty array. Use this.setState function to change the value of this.state.Tot_Item
=> In file Navv.jsx value of this.props.Tot_Item is empty array. Change the way to render an array of button.
https://codesandbox.io/s/stoic-rubin-wg2fo
You don't need to use async and await in React, as it's supposed to work asynchronously. You can, however, pass a callback function to setState to do what you want. That method shall be passed as a second parameter in your setState call, and the function you pass will not be run until React has successfully updated the state.
this.setState({
myVar: 'value to print'
}, () => console.log(this.state.myVar));
Also, I've noticed you're calling setState a lot of times on your listval method. You actually don't have to call setState multiple times if you want to set many properties at once. Since the state is an object, you can change all of the properties you want in a single call, like this:
this.setState({
Select_Price: ll,
Select_Item: ll3,
Select_Item_TotalPrice: ll6,
Total_Item: ll7
});
As for why this.props.Tot_Item is always undefined in your Navv component, the reason is that a children component cannot modify its parent' state in any way.
Right now your components are structured in the following way: Navbar contains both Navv and Menu. Menu is the one that contains a list of items and is supposed to update the list of selected items, while Navv is just supposed to display the number of items in said array.
However, there's a problem: you cannot pass data directly from Menu to its sibling, Navv. It's necessary to have a parent component (in this case, Navbar) that gets the data from Menu and pass it down to Navv. However, it doesn't really work as you have it. The only way a children component (Menu) can alter the parent' (Navbar) state is by using a callback method, as seen in the example below:
Changes in Navbar component
// CHANGE: Create callback function to send data to parent
updateTotalItems = total => {
this.setState({ Total_Item: total }, () =>
console.log("total items in navbar", this.state.Total_Item)
);
};
// CHANGE: pass callback method as props to the children component that modifies the item counter
render() {
return (
<React.Fragment>
<Navv Tot_Item={this.state.Total_Item} />
<Menu
updateTotalItems={this.updateTotalItems}
Objs_Type={this.state.Category}
/>
</React.Fragment>
);
Changes in Menu component
Listval() {
/* ... */
// CHANGE: No need to call setState multiple times
this.setState(
{
Select_Price: ll,
Select_Item: ll3,
Select_Item_TotalPrice: ll6,
Total_Item: ll7
},
() => {
// CHANGE: Use callback function to send the array length to the parent component
this.props.updateTotalItems(this.state.Total_Item.length);
}
);
}
Here you have a working version of your sandbox example with all of these changes. Hope it helps!
You can do the below changes.
You can place your state in constructor.
You need to declare ListVal using FAT operator and setState of what you like. (In my case I have explicitly set it to 2 on click of OK button in Modal popup and its appearing on the screen too)
import React, { Component } from "react";
import Menu from "./Menu";
import Navv from "./Navv";
class Navbar extends Component {
constructor(props)
{
super(props);
this.state = {
Category: [
{
id: 1,
FoodType: "Chinese",
Menu: ["Egg Drop", "Chicken Fried", "Beef Fried"],
Price: [2, 8, 10]
},
{
id: 2,
FoodType: "Mexican",
Menu: ["Veg Burrito", "Chicken Burrito", "Beef Burrito"],
Price: [7, 8, 10]
}
],
One_Item: [
{
listvalue: null,
Select_Item: null,
Select_Price: null,
Select_Quantity: null,
Select_Item_TotalPrice: null
}
],
Total_Item: 0
};
}
Listval=async()=>{
this.setState({ Total_Item: 2});
}
render() {
return (
<React.Fragment>
<Navv Tot_Item={this.state.Total_Item} />
<Menu
Listvalll={this.Listval}
Objs_Type={this.state.Category}
/>
</React.Fragment>
);
}
}
export default Navbar;

Fetch API data with react and filter search input with autocomplete

I have a question regarding a React filter search input.
I have two Components the the moment.
FirstPageComponent.js
import React from "react"
import AutoComplete from "./AutoComplete"
export class FirstPageComponent extends React.Component {
constructor() {
super()
this.state = {
rows: [],
loading: true,
}
}
async componentDidMount(searchTerm = "marvel") {
await fetch(
"https://api.themoviedb.org/3/search/movie?api_key=&query=" +
searchTerm
)
.then(response => response.json())
.then(responseData => {
this.setState({
rows: responseData.results,
loading: false,
})
})
.catch(error => {
console.log("Error fetching and parsing data", error)
})
}
render() {
console.log(this.state.rows)
return <AutoComplete movies={["hej"]} />
}
}
and
AutoComplete.js
import React from "react"
export default class AutoComplete extends React.Component {
constructor(props) {
super(props)
this.state = {
rows: [],
loading: false,
userInput: "",
}
}
onChange = e => {
const { movies } = this.props
const userInput = e.currentTarget.value
const rows = movies.filter(
suggestion =>
suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1
)
this.setState({
rows,
loading: true,
userInput: e.currentTarget.value,
})
}
onClick = e => {
this.setState({
rows: [],
loading: false,
userInput: e.currentTarget.innerText,
})
}
render() {
const {
onChange,
onClick,
state: { rows, loading, userInput },
} = this
let moviesListComponent
if (loading && userInput) {
if (rows.length) {
moviesListComponent = (
<ul>
{rows.map((movie, index) => {
return (
<li key={index} onClick={onClick}>
{movie}
</li>
)
})}
</ul>
)
} else {
moviesListComponent = (
<>
<p>Couldn't find any movies. Try searching for another movie.</p>
</>
)
}
}
return (
<React.Fragment>
<input type="search" onChange={onChange} value={userInput} />
{moviesListComponent}
</React.Fragment>
)
}
}
I basically want to know if i’m approaching this the right way. I want to make a dynamic request for movie titles from fetch(”https://themovie.db”) API.
Send down the movie titles as props and then filter the values in autocomplete component if the user input is similar to the movie title props.
Am i thinking about this the right way? I've tried to have the fetch call in the same component as AutoComplete but haven't gotten it to work as i want it to. How would you setup the component structure if you'd be solving this issue for example?
Feel free to ask if there’re any confusions.
Thanks
These are my assumptions about the above code, let me know if any are incorrect.
You're doing an initial API request for a list of movies by search term. That search term is given outside of the code above and doesn't change by any mechanism above.
You're further filtering the data returned by the API request dynamically via the AutoComplete component.
Most of your React code here looks pretty good, but I've got some recommendations:
In general, I find it's best to keep the list of things completely separate, so I would create a separate MovieList function component that simply takes an array of movie objects, and a titleFilter string and renders them.
componentDidMount doesn't take arguments. searchTerm should be a prop of FirstPageComponent (presumably set by the parent component by some kind of selection or user input)
I'd rename AutoComplete to TitleFilter or the like. It's not really auto-completing, it's more of a filter field.
This line: if (loading && userInput) { will result in displaying no movies when the user hasn't yet filled in text into the filter field. I'm not sure that is what you would want.
This line is a bit misleading: <p>Couldn't find any movies. Try searching for another movie.</p> because you could have just filtered out all the results returned. In this case, it wasn't that you couldn't find movies, it's that your filter is too restrictive.
For the onClick handler, I'm not sure such a general approach is best. It could create a strange interaction if you were to somehow click on the <ul>, for example. To improve this, I'd recommend each movie row component implement its own onClick and call a function to set the filter.
You're going to have to deal with pagination on this, I'd bet. Check your API and see what it does for that and make sure you're accounting for it.
Small nits:
userInput: e.currentTarget.value, could be shortened to userInput,, which is shorthand for userInput: userInput, (you already have a variable named userInput.)
For key props, an actual unique id would be better, in the case you change your filtering and index numbers don't line up with the unique rows you're rendering anymore.
I like to go ahead and create a separate component for each row, so I'd make a MovieRow function component which takes a movie object and renders it. It just keeps the code a bit cleaner. (and makes #6 above easier to implement)

React - What is the correct way to make an API call with user input?

I'm relatively new to React and am building a simple weather app to help me learn. I've read the docs extensively but I'm still stuck. My app has user input where they can search for a city's weather forecast using the OpenWeatherMap API. Currently, my component state looks like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoaded: false,
inputText: '',
data: {
dayOne: [],
dayTwo: [],
dayThree: [],
dayFour: [],
dayFive: [],
daySix: []
}
};
}
}
Once they click on the button to get the data, I use a handleSubmit() method with axios to fetch the data from the API. This successfully populates the component state.
For example, in the React Dev Tools I can see that I get the following data in this.state.data.dayOne[0], which is exactly what I want:
0: {
weather_data: {
cityName: "London"
date: "2019-08-05"
description: "scattered clouds"
temp: 21.79
tempMax: 21.79
tempMin: 21.41
time: "18:00"
weatherIcon: "03d"
}
}
Below my user input component, I have a panel component which displays the current weather data for that day. For clarity, I've reduced the size of the component here:
// In CurrentWeatherDisplay.js:
class CurrentWeatherDisplay extends Component {
render() {
return (
<div className="col-12 border border-info">
<div className="row">
<div className="col-6 py-4">
<p className="mb-0 h6">City name:</p>
</div>
<div className="col-6 py-4">
<p className="mb-0 h6">{this.props.data.dayOne[0].weather_data.cityName}</p>
</div>
</div>
</div>
);
}
}
The problem I have is that this.props.data.dayOne[0].weather_data.cityName does not exist until the API has been called, and so the app can't render. I've read the React docs and it says to use the componentDidMount() lifecycle method. But in their example, this is an API call that happens immediately - https://reactjs.org/docs/faq-ajax.html
My app is different because I update the state after the page has loaded, and only when the user has submitted the form. Any ideas how I can correct the structure of my app?
It's fine if you want to put the code for loading into a callback function instead of componentDidMount. The main thing you should take away from that article is to have a state value which starts off empty (or in some other way indicating that data has not been loaded), and a render function that knows what to render when it sees the state is empty.
So for example, if you have a flag in your state called isLoaded, your render function can check if isLoaded is false, and when it see that it returns some placeholder.
render() {
if (!this.state.isLoaded) {
return <div>Loading...</div>
}
// else, return the populated component
}
Then when you want to load data (componentDidMount in their example, handleSubmit in yours), do so and then call this.setState. The new state will cause the component to rerender, and your render function now returns something different than before, using the new data.

How to load data "on-demand" in Redux/React app

I'm trying to make a simple React+Redux app that gets data from the server. I understand basic principles of the Redux, but I cannot find anywhere in tutorials an example of how to implement something like "loading data on-demand".
My API can return list of the books, like this:
[
{
title: 'The Hobbit',
authorId: 1
},
{
title: 'The Colour of Magic',
authorId: 2
}
]
And there is another call to get data by specific author, this call will return something like this (by authorId):
{
title: 'J. R. R. Tolkien',
authorId: 1
}
or
{
title: 'Terry Pratchett',
authorId: 2
}
I have these methods in my "actions" file:
export function loadBooksSuccessfully(books) {
return {type: types.LOAD_BOOKS, books};
}
export function loadBooks() {
return function (dispatch) {
return MyApi.getBooks().then(data => {
dispatch(loadBooksSuccessfully(data));
}).catch(err => {
throw(err);
});
};
}
I call loadBooks() action in the main js file to display the list of the books when the page was loaded. My problem is that I don't know where should I request info about authors to display list in the following format:
Book: "The Hobbit"
Author: "J. R. R. Tolkien"
Book: "The Colour of Magic"
Author: "Terry Pratchett"
I have react components 'BooksList' and 'BookItem' for rendering the layout.
I pass the list of the books to the BooksList via mapStateToProps method:
class BooksList extends React.Component {
render() {
let books = this.props.books;
return <div>
{books.map((book) => {
return <BookItem key={`my-book-${book.id}`}
{...book} />
})}
</div>
}
}
function mapStateToProps(state, ownProps) {
return {
books: state.app.books
};
}
function mapDispatchToProps(dispatch) {
return {};
}
export default connect(mapStateToProps, mapDispatchToProps)(BooksList);
Here is my BookItem react component:
class BookItem extends React.Component {
public render() {
const book = this.props.book;
return (<div>
<div>
Book: {book.name}
</div>
<div>
Author: {book.author.name}
</div>
</div>);
}
}
Here is actions for loading author info:
export function loadAuthorSuccessfully(author) {
return {type: types.LOAD_AUTHOR, author};
}
export function loadAuthor(authorId) {
return function (dispatch) {
return MyApi.getAuthor(authorId).then(data => {
console.info('DATA', data);
dispatch(loadAuthorSuccessfully(data));
}).catch(err => {
throw(err);
});
};
}
I was thinking to call loadAuthor() method in the componentDidMount method of the BookItem component but it doesn't seem like real redux solution. What is the canonical way to do it?
Loading data with Redux is simply a specific case of dispatching an action.
To dispatch an action from React, you have generally two options:
1) You dispatch in some event handler - the user clicking a button or link, toggling a checkbox... a response to an event.
2) You dispatch when a specific Componente is about to mount / mounted - something appears on screen without the user needing to click around.
The first case is very simple, map your action creators using connect and call that new injected prop in your handler.
For the second, you still need to bind your action creator, and then you use one of React lifecycle events like this
class AuthorPage extends React.Component {
componentDidMount(){
this.props.loadAuthor()
}
}
This second approach means you need to take into account that the data may not be ready when the component is first rendered, so you will likely keep that in your Redux state and display a spinner or null until it is.
Hope it helps, I suggest you watch Dan Abramov's Egghead video series. 2h of your time will save you dozens at Stackoverflow. :)

React Dynamic Component Naming

I'm new to React so please have mercy.
I've also read all of the threads on this, React / JSX Dynamic Component Name and React/JSX dynamic component names in particular. The solutions did not work.
I'm using a tab style interface where a user selects a tab and the appropriate content loads. A parent component stores the tab's content state, passes the corresponding props to the content child. This child then loads the correct content component (as its own child).
var TabbedContent = React.createClass({
loadMenu: function() {
var menus=this.props.carDivState.vehicleDetailState;
for (key in menus) {
if (menus.hasOwnProperty(key)) {
if (menus[key]) {
var Component='TabbedContent'+key;
return <Component />;
}
}
}
},
render: function() {
return (
<div className="TabbedContent">
<div className="contentWrapper">
{this.loadMenu()}
</div>
</div>
)
}
});
loadMenu loops through the props until it finds a true prop. It then returns that key (for instance "Overview") and creates a variable (e.g. Component='TabbledContentOverview').
However, my code returns an HTML tag <tabbedcontentoverview></tabbedcontentoverview>
Question
How do I get React to return the React component instead of an HTML tag? I appear to be using the correct capitalized naming conventions. I've read the Facebook docs. I just don't get it.
https://github.com/vasanthk/react-bits/blob/master/patterns/30.component-switch.md
import HomePage from './HomePage.jsx';
import AboutPage from './AboutPage.jsx';
import UserPage from './UserPage.jsx';
import FourOhFourPage from './FourOhFourPage.jsx';
const PAGES = {
home: HomePage,
about: AboutPage,
user: UserPage
};
const Page = (props) => {
const Handler = PAGES[props.page] || FourOhFourPage;
return <Handler {...props} />
};
// The keys of the PAGES object can be used in the prop types to catch dev-time errors.
Page.propTypes = {
page: PropTypes.oneOf(Object.keys(PAGES)).isRequired
};
First, if you are using Bootstrap for your app, I'd suggest that you use react-bootstrap`s tab. If you are not, I would suggest that you at least take a look at the implementation of their TabPane and TabbedArea.
Here's an example of how it looks like in your app:
const tabbedAreaInstance = (
<TabbedArea defaultActiveKey={2}>
<TabPane eventKey={1} tab='Tab 1'>TabPane 1 content</TabPane>
<TabPane eventKey={2} tab='Tab 2'>TabPane 2 content</TabPane>
<TabPane eventKey={3} tab='Tab 3' disabled>TabPane 3 content</TabPane>
</TabbedArea>
);
React.render(tabbedAreaInstance, mountNode);
Now, back to your question, if you want to create a component by name, just call React.createElement from inside your loadMenu:
loadMenu: function() {
var menus=this.props.carDivState.vehicleDetailState;
for (key in menus) {
if (menus.hasOwnProperty(key)) {
if (menus[key]) {
return React.createElement('TabbedContent'+key);
}
}
}
}
You need to have a reference to an actual class in order to create an element from it (in JS or JSX).
Hold a map of keys to React classes (i.e tabbedChildren), and just create this element using the JS API:
var childComponent = tabbedChildren[key]
return React.createElement(childComponent)
https://facebook.github.io/react/docs/top-level-api.html

Categories