I'm trying to pass data in React from parent to child , I already managed to set right value from one file to another, but same that information that I passed I need to pass once more again. I will show you some code so you can understand actual problem.
From List.js file I'm taking the right information like
<Products categoryid={item.id}/>
so that same item.id I passed to Products, as you see I have this.props.categoryid which is giving me right information as value to add this item as you see, and it looks like
import React, { Component } from 'react'
import { getProducts, addItem, deleteItem, updateItem } from './ProductFunctions'
class Products extends Component {
constructor() {
super()
this.state = {
id: '',
title: '',
price: '',
off_price: '',
category_id: '',
arttitle: '',
artbody: '',
editDisabled: false,
items: []
}
this.onSubmit = this.onSubmit.bind(this)
this.onChange = this.onChange.bind(this)
}
componentDidMount() {
this.getAll()
}
onChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
getAll = () => {
getProducts().then(data => {
this.setState(
{
title: '',
price: '',
off_price: '',
category_id: this.props.categoryid,
items: [...data]
},
() => {
console.log(this.state.items)
}
)
})
}
So the real problem is how to pass this this.props.categoryid as a category_id to getProducts function in ProductFunctions.js so I can get list from ?
export const getProducts = category_id => {
return axios
.get('/api/products/${category_id}', {
headers: { 'Content-Type': 'application/json' }
})
.then(res => {
return res.data
})
}
It seems you forgot to use `` and instead used '' in the getProducts function in ProductFunctions.js, so let's correct that.
export const getProducts = category_id => {
return axios
.get(`/api/products/${category_id}`, {
headers: { "Content-Type": "application/json" }
})
.then(res => {
return res.data;
});
};
Now, just pass the categoryid you obtained from props to the getProducts in the getAll method, when its invoked. (As per what the exported function expects in ProductFunctions.js
getAll = () => {
const { categoryid } = this.props;
getProducts(categoryid).then(data => {
this.setState(
{
title: "",
price: "",
off_price: "",
category_id: categoryid,
items: [...data]
},
() => {
console.log(this.state.items);
}
);
});
};
Access the prop within getAll function
getAll = () => {
getProducts(this.props.categoryid).then(data => {
this.setState({
title: '',
price: '',
off_price: '',
category_id: this.props.categoryid,
items: [...data]
},
() => {
console.log(this.state.items)
}
)
})
}
Related
This is the total code that I currently have.
import { React, useEffect, useState } from 'react';
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-balham.css";
const FetchStocks = () => {
const API_KEY = "apiKey1";
const API_KEY2 = "apiKey2";
const API_KEY3 = "apiKey3";
const [data, setData] = useState({ StockSymbols: null, StockName: null, StockIndustry: null })
const [MSFT, setMSFT] = useState({ MSFTSymbols: null, MSFTName: null, MSFTIndustry: null })
const [AA, setAA] = useState({ AASymbols: null, AAName: null, AAIndustry: null })
const [BABA, setBABA] = useState({ BABASymbols: null, BABAName: null, BABAIndustry: null })
const [SAIC, setSAIC] = useState({ SAICSymbols: null, SAICName: null, SAICIndustry: null })
const [search, setSearch] = useState < string > ('');
useEffect(() => {
fetch(`https://www.alphavantage.co/query?function=OVERVIEW&symbol=IBM&apikey=${API_KEY}`)
.then(
function (response) {
return response.json();
}
)
.then(
function (data) {
setData({
StockSymbols: data['Symbol'],
StockName: data['Name'],
StockIndustry: data['Industry']
})
})
fetch(`https://www.alphavantage.co/query?function=OVERVIEW&symbol=MSFT&apikey=${API_KEY2}`)
.then(
function (response) {
return response.json();
}
)
.then(
function (MSFT) {
setMSFT({
MSFTSymbols: MSFT['Symbol'],
MSFTName: MSFT['Name'],
MSFTIndustry: MSFT['Industry']
})
})
fetch(`https://www.alphavantage.co/query?function=OVERVIEW&symbol=AA&apikey=${API_KEY3}`)
.then(
function (response) {
return response.json();
}
)
.then(
function (AA) {
setAA({
AASymbols: AA['Symbol'],
AAName: AA['Name'],
AAIndustry: AA['Industry']
})
})
fetch(`https://www.alphavantage.co/query?function=OVERVIEW&symbol=BABA&apikey=${API_KEY}`)
.then(
function (response) {
return response.json();
}
)
.then(
function (BABA) {
setBABA({
BABASymbols: BABA['Symbol'],
BABAName: BABA['Name'],
BABAIndustry: BABA['Industry']
})
})
fetch(`https://www.alphavantage.co/query?function=OVERVIEW&symbol=SAIC&apikey=${API_KEY2}`)
.then(
function (response) {
return response.json();
}
)
.then(
function (SAIC) {
setSAIC({
SAICSymbols: SAIC['Symbol'],
SAICName: SAIC['Name'],
SAICIndustry: SAIC['Industry']
})
})
}, [])
const table = {
columns: [
{ headerName: "Symbol", field: "symbol" },
{ headerName: "Name", field: "name" },
{ headerName: "Industry", field: "industry" }
],
rowData: [
{ symbol: `${data.StockSymbols}`, name: `${data.StockName}`, industry: `${data.StockIndustry}` },
{ symbol: `${MSFT.MSFTSymbols}`, name: `${MSFT.MSFTName}`, industry: `${MSFT.MSFTIndustry}` },
{ symbol: `${AA.AASymbols}`, name: `${AA.AAName}`, industry: `${AA.AAIndustry}` },
{ symbol: `${BABA.BABASymbols}`, name: `${BABA.BABAName}`, industry: `${BABA.BABAIndustry}` },
{ symbol: `${SAIC.SAICSymbols}`, name: `${SAIC.SAICName}`, industry: `${SAIC.SAICIndustry}` }
],
}
let containerStyle = {
height: 500,
width: 700
}
return (
<div>
<div>
<input type="search" placeholder="Search Stock" />
</div>
<div
className="ag-theme-balham"
style={containerStyle}
>
<AgGridReact
columnDefs={table.columns}
rowData={table.rowData}
pagination={true}
/>
</div>
</div>
)
};
export default FetchStocks;
I'm trying to make search bar for the symbols column in the table.
This is the table
However, I'm concerned because every element in the table is fetched and saved in differenct const (eg. data, MSFT, AA).
How would I be able to create a search bar that searches by the stock symbol in the table?
One of the easiest way I can think of is to use filter method on 'rowData' property of 'table'.
rowData: [
{
symbol: `${data.StockSymbols}`,
name: `${data.StockName}`,
industry: `${data.StockIndustry}`
}
].filter((data) => {
return data.name.includes(search);
})
Add setSearch to onChange eventHandler of input Element.
In here, I have shown to use name of the stock, you can also use industry and filter based on that.
Attached, codesandbox link
I've been trying to create this search app where I can display the items in a table and delete items using react redux. However, on the initial load, the app shows a table but there is no data in the table. It's an empty table. If i search for another movie name which have more than one movie for that search term, then 2 tables would be shown but I want to show everything on the same table itself. The delete button is not working as well. Is there something wrong with my action and reducer files?
Action.js
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "./types";
const fetchMoviePendig = () => ({
type: FETCH_MOVIE_PENDING
});
const fetchMovieSuccess = json => ({
type: FETCH_MOVIE_SUCCESS,
payload: json
});
const fetchMovieError = error => ({
type: FETCH_MOVIE_ERROR,
payload: error
});
export const fetchMovie = name => {
return async dispatch => {
dispatch(fetchMoviePendig());
try {
const url = `https://jsonmock.hackerrank.com/api/movies/search/?Title=${name}`;
const response = await fetch(url);
const result = await response.json(response);
console.log(result);
dispatch(fetchMovieSuccess(result.data));
} catch (error) {
dispatch(fetchMovieError(error));
}
};
};
export const deleteEvent = id => async dispatch => {
try {
dispatch({
type: DELETE_MOVIE,
payload: id
});
} catch (err) {
console.log(err);
}
};
Reducer
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "../action/types";
const initialState = {
data: [],
loading: false,
error: ""
};
const moviesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_MOVIE_PENDING:
return {
...state,
loading: true
};
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: [...state.data, action.payload]
};
case FETCH_MOVIE_ERROR:
return {
...state,
loading: false,
error: action.payload
};
case DELETE_MOVIE:
return {
...state,
data: state.data.filter(movie => movie.id !== action.payload)
};
default:
return state;
}
};
export default moviesReducer;
App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMovie } from "./action/movieActions";
import Input from "./components/Input";
import MovieTable from "./components/MovieTable";
class App extends Component {
state = {
searchInput: "The Rain"
};
componentDidMount() {
this.props.getMovieList(this.state.searchInput);
}
_getMovie = () => {
this.props.getMovieList(this.state.searchInput);
};
_onChangeHandler = e => {
this.setState({
searchInput: e.target.value
});
console.log(this.state.searchInput);
};
render() {
const { data, loading } = this.props.movies;
return (
<div className="center">
<div>
<h2 className="center white-text">Movie Search</h2>
</div>
<div className="container">
<Input
value={this.state.searchInput}
onChange={this._onChangeHandler}
onClick={this._getMovie}
/>
<div className="row">
{loading ? (
<p>Loading</p>
) : (
data.map(item => (
<MovieTable
key={item.imdbID}
year={item.Year}
name={item.Title}
movieId={item.imdbId}
/>
))
)}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
movies: state.movies
};
};
const mapDispatchToProps = dispatch => {
return {
getMovieList: name => dispatch(fetchMovie(name))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Hello please take a look at the sandbox : https://codesandbox.io/s/prod-wind-4hgq2?file=/src/App.js
I have edited
<MovieTable
data={data.map(d => ({
year: d.Year,
name: d.Title,
movieId: d.imdbId
}))}
/>
and
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: action.payload
};
And ... Currently the delete button has no event, that's why it can't work
I see data having the following pattern:
Object {page: 1, per_page: 10, total: 1, total_pages: 1, data: Array[1]}
page: 1
per_page: 10
total: 1
total_pages: 1
data: Array[1]
0: Object
Title: "Sin in the Rain"
Year: 2006
imdbID: "tt1072449"
And you are accessing wrong properties in the component render logic, can you fix that.
Duplicate table is created the way you have written the logic.
Pass the data to MovieTable component and let it render and create the table
and fill it.
In reducer (FETCH_MOVIE_SUCCESS) you need don't need to append data you have to
replace or use the current movie data only.
constructor(props) {
super(props);
this.state = {
movie: "Interstellar",
movies: [],
newMovieParsed: {
movieTitle: '',
moviePosters: '',
moviePlot: '',
movieGenre: '',
movieBoxOffice: '',
movieRatings: [],
movieActors: '',
imdbId: '',
}
};
}
onSubmit = movie => {
this.setState(state => ({ ...state, movie }));
this.componentWillMount();
};
componentWillMount() {
this.APIURL = `http://www.omdbapi.com/? s=${this.state.movie}&apikey=${API_KEY}`;
console.log(this.APIURL);
fetch(this.APIURL)
.then(resp => resp.json())
.then(data => {
const movies = data.Search;
this.setState(state => ({
...state, movies
}));
});
}
Now in the render
<Search
placeholder="Enter the title of a movie you wish to search and press Enter .."
onSearch={(value) => this.onSubmit(value)}
style={{ width: '100%' }}
/>
Everything works but when I put a new movie and press enter I have to enter twice. The first enter seems to be updating the state then the second updates the render. How do i update the state and render it with the first enter? I am also using Ant-design.
The main thing is your setState callback should either be the name of the function (no parenthesis) or an anonymous function that calls it:
onSubmit = movie => {
this.setState(state => ({ ...state, movie }));
this.componentWillMount;
};
OR
onSubmit = movie => {
this.setState(state => ({ ...state, movie }));
() => this.componentWillMount();
};
Other hints:
Usually you don't call lifecycle methods directly (componentWillMount), and you don't need to do that much work in setState. You can set just the key you want to replace.
Here is some optimized code if you are interested:
constructor(props) {
super(props);
this.state = {
movie: "Interstellar",
movies: [],
newMovieParsed: {
movieTitle: '',
moviePosters: '',
moviePlot: '',
movieGenre: '',
movieBoxOffice: '',
movieRatings: [],
movieActors: '',
imdbId: '',
}
};
}
onSubmit = movie => {
this.setState({movie}), this.fetchMovie);
};
componentWillMount() {
this.fetchMovie();
}
fetchMovie() {
this.APIURL = `http://www.omdbapi.com/? s=${this.state.movie}&apikey=${API_KEY}`;
console.log(this.APIURL);
fetch(this.APIURL)
.then(resp => resp.json())
.then(data => {
const movies = data.Search;
this.setState(state => ({
...state,
movies
}));
});
}
There is no reason to get whole list of movies every time you update state. In componentWillMount() you can do initial fetch of movies and then on submit you can just update the state with new movie. If needed you can call React.forceUpdate() to trigger render method.
constructor(props) {
super(props);
this.state = {
movie: "Interstellar",
movies: [],
newMovieParsed: {
movieTitle: '',
moviePosters: '',
moviePlot: '',
movieGenre: '',
movieBoxOffice: '',
movieRatings: [],
movieActors: '',
imdbId: '',
}
};
}
componentWillMount() {
this.getMovies();
});
getMovies = () => {
this.APIURL = `http://www.omdbapi.com/? s=${this.state.movie}&apikey=${API_KEY}`;
console.log(this.APIURL);
fetch(this.APIURL)
.then(resp => resp.json())
.then(data => {
const movies = data.Search;
this.setState(state => ({
...state, movies
}));
}
onSubmit = movie => {
this.setState(state => ({ ...state, movie }));
};
}
I'm trying to get a simple API call working, where the component calls the API as its mounting and sets the state to be rendered. But when I try to get the state to change an object in it, it says that the state is undefined.
TypeError: Cannot read property 'state' of undefined
class SpellGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
spacing: '16',
username: 'admin',
password: 'notpassword',
description: '',
remember: false,
spell: {
name: '',
school: '',
},
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.mapApiToState = this.mapApiToState.bind(this);
}
mapApiToState() {
// I've tried with all of the axios code in here.
}
componentDidMount() {
axios
.get("http://localhost:8000/api/spells/1")
.then(function(response) {
console.log('response', response);
let fields = response.data[0].fields;
// THIS IS THE LINE THAT IS ERRORING
let spell = Object.assign({}, this.state.spell);
spell.name = fields.Name;
spell.school = fields.School;
console.log('spell', spell);
this.setState({spell});
console.log('state.spell', this.state.spell);
//console.log('state', this.state);
})
.catch(function(error) {
console.log(error);
});
console.log('state', this.state);
}
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
onSubmit = (event) => {
event.preventDefault();
this.props.onSubmit(this.state.username, this.state.password)
};
handleSubmit(e) {
console.log('Form state: ', this.state);
e.preventDefault();
}
render() {
const {classes, theme} = this.props;
const { spacing } = this.state;
return (
<div>{this.state.spell.name}</div>
);
}
} export default withStyles(styles, { withTheme: true })(SpellGrid);
If you are using this, you will need to be carefull in which function scope you're in:
axios
.get("http://localhost:8000/api/spells/1")
.then(response => {
// Since the `response` is now an arrow function, we still
// get access to the original `this`
let fields = response.data[0].fields;
let spell = Object.assign({}, this.state.spell);
spell.name = fields.Name;
spell.school = fields.School;
this.setState({
spell
});
})
.catch(error => {
console.log(error);
});
I'm building a simple CRUD note app and I'm having issues getting the child components to update after simple POST and DELETE api calls to create and delete notes.
Here's the parent component with a simple form and a child component <NotepadsShowView /> to render the submitted notes.
class AuthenticatedHomeView extends Component {
_handleSubmit(e) {
e.preventDefault()
const { dispatch } = this.props
const data = {
title: this.refs.title.value,
description: this.refs.description.value,
private: this.refs.private.checked
}
dispatch(Actions.createNotepad(this.props.currentUser.id, data))
this._resetForm()
}
_resetForm() {
this.refs.title.value = ''
this.refs.description.value = ''
this.refs.private.checked = true
}
render() {
return (
<div>
<form onSubmit={::this._handleSubmit}>
{/* form removed for clarity */}
</form>
<NotepadsShowView/>
</div>
)
}
}
and the NotepadsShowView child component:
class NotepadsShowView extends Component {
componentWillMount() {
const { dispatch, currentUser } = this.props
dispatch(Actions.fetchNotepads(currentUser.id))
}
_renderEachOwnedNotepad() {
const { ownedNotepads } = this.props
return ownedNotepads.map((notepad, i) => {
return <NotepadShowView key={notepad.id} {...notepad} {...this.props}/>
})
}
render() {
const { isFetchingNotepads } = this.props
const notepads = this._renderEachOwnedNotepad()
if (isFetchingNotepads || notepads.length == 0) return null
return (
<ul className="notepads-container">
{notepads}
</ul>
)
}
}
const mapStateToProps = (state) => ({
isFetchingNotepads: state.notepads.fetching,
currentUser: state.session.currentUser,
ownedNotepads: state.notepads.ownedNotepads,
sharedNotepads: state.notepads.sharedNotepads
})
export default connect(mapStateToProps)(NotepadsShowView)
Here is the action creator:
const Actions = {
createNotepad: (userId, data) => {
return dispatch => {
httpPost(`/api/v1/users/${userId}/notepads`, {data: data})
.then(data => {
dispatch({
type: CONSTANTS.NOTEPADS_CREATED,
notepad: data
})
})
.catch(error => {
error.response.json()
.then(json => {
dispatch({
type: CONSTANTS.NOTEPADS_CREATE_ERROR,
errors: json.errors,
})
})
})
}
},
fetchNotepads: (userId) => {
return dispatch => {
dispatch({
type: CONSTANTS.NOTEPADS_FETCHING
})
httpGet(`/api/v1/users/${userId}/notepads`, {id: userId})
.then(data => {
dispatch({
type: CONSTANTS.NOTEPADS_RECEIVED,
notepads: data.notepads
})
})
.catch(error => {
error.response.json()
.then(json => {
dispatch({
type: CONSTANTS.NOTEPADS_ERRORS,
errors: json.error
})
})
})
}
},
deleteNotepad: (userId, notepadId) => {
return dispatch => {
httpDelete(`api/v1/users/${userId}/notepads/${notepadId}`, {id: notepadId})
.then(data => {
dispatch({
type: CONSTANTS.NOTEPADS_OWNED_DELETE,
id: notepadId
})
})
}
},
}
Here is the reducer:
const initialState = {
ownedNotepads: [],
fetching: true,
}
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case CONSTANTS.NOTEPADS_FETCHING:
return {
...state,
fetching: true,
}
case CONSTANTS.NOTEPADS_RECEIVED:
return {
...state,
fetching: false,
ownedNotepads: action.notepads
}
case CONSTANTS.NOTEPADS_CREATED:
return {
...state,
ownedNotepads: [
...state.ownedNotepads,
{
id: action.id,
title: action.title,
description: action.description,
private: action.private
}
]
}
case CONSTANTS.NOTEPADS_OWNED_DELETE:
const index = state.ownedNotepads.findIndex(note => note.id === action.id)
return {
...state,
ownedNotepads: [
...state.ownedNotepads,
state.ownedNotepads.slice(0, index),
state.ownedNotepads.slice(index + 1)
]
}
default:
return state
}
}
A user submits a new notepad which triggers an POST api call. Server returns the new notepad and the reducer adds the notepad to the Redux state. No issues here. However, when a notepad is created the notepad props are undefined and no new notepads are being shown in the child UI components. They don't know of the update and I assume it's because I'm not handling the state update.
I am using componentWillMount (cWM) above to fetch the updated notepads state before the initial render. I'm assuming I should use componentWillReceiveProps but I understand there will be an infinite loop if I dispatch the fetchNotepads action because the dispatch in cWM will run again.
My question is how do I update the component props when the Redux state changes? Do I have to use component state? What about the lifecycle methods?