I am starting in the development of applications with apollo, react and graphql, I have created a form to capture brands of vehicle and show them in a table. When you start the page for the first time, it shows the table well, but when I try to record a new brand it throws me the following image. Please I need help.
this is my code
import React, { Component, Fragment } from 'react';
import { Query, Mutation } from 'react-apollo';
import BrandItem from '../models/BrandItem';
import * as BrandService from '../services/BrandService';
export class Brand extends Component {
render() {
let input;
return (
<div className="divform">
<Mutation mutation={BrandService.ADD_BRAND}
update={(cache, { data: { addBrand } }) => {
const { brands } = cache.readQuery({ query: BrandService.BRAND_QUERY });
cache.writeData({
query: BrandService.BRAND_QUERY,
data: { brands: brands.concat([addBrand]) },
});
}}
>
{
(addBrand, { data }) => (
<form onSubmit={e => {
e.preventDefault();
addBrand({ variables: { description: input.value } });
console.log(data);
input.value = '';
}}>
<label htmlFor="description">Description</label>
<input type="text" id="description" name="description" required ref={node => { input = node; }} />
<input type="submit" value="Send" />
</form>
)
}
</Mutation>
<div>
<Fragment>
<Query query={BrandService.BRAND_QUERY}>
{
({ loading, error, data }) => {
if (loading) return <div><h4>loading....</h4></div>
if (error) console.log(error);
return <Fragment>
<table>
<thead>
<tr>
<th>Id</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{
data.brands.map(brand => (
<BrandItem key={brand.id} brand={brand} />
))
}
</tbody>
</table>
</Fragment>
}
}
</Query>
</Fragment>
</div>
</div>
)
}
}
export default Brand
what am I doing wrong?
Thanks
the only place you use map is here.
{
data.brands.map(brand => (
<BrandItem key={brand.id} brand={brand} />
))
}
Therefore, data.brands must be null or undefined.
I would check the query BrandService.BRAND_QUERY and check it has brands defined in the query. It may help if you added a console.log(data) to double check the structure of the response.
If it makes sense that there are no brands returned in some cases. The simplest way to do that would be to do.
{
data.brands && data.brands.map(brand => (
<BrandItem key={brand.id} brand={brand} />
))
}
However the best thing to do depends on your app.
Related
I am trying to learn React and Typescript. I am building a little demo app that sends a request to the api https://api.zippopotam.us/us to get postcode information and display the place information. If the request is submitted with an invalid postcode I want to display an error message.
Below is my code. I have the call to the api inside the useEffect method and notice that this runs twice when the app is loaded i.e. before the user has entered a zipcode and clicked the search button so the api call is made without the zipcode and hence returns a 404, so the error code is always displayed on initial load.
I thought that the useEffect method should only get run when the zipSearch value changes i.e. when a user enters a zip and clicks enter. Although reading up on the useEffect method is seems it runs everytime the app component renders. Im also a little confused why it runs twice on initial load.
How can I get this to work the way I want it to? Any help would be much appreciated. If a moderator deletes this question, can they please let me know why? Thanks.
import React, {FormEvent, useEffect, useState} from "react";
import { useForm } from 'react-hook-form'
import "./App.css";
import axios from "axios";
import { IPlace } from "./IPlace";
export default function App2(){
const [placeFound, setPlaceFound] = useState<IPlace[]>([]);
const [zipSearch, setZipSearch] = useState("");
const [errorFound, setErrorFound] = React.useState("");
const renderPlaces = () => {
console.log("Render places runs")
if(placeFound.length !== 0){
return (<div className="table-container">
<table>
<thead>
<tr>
<th><span>State</span></th>
<th><span>Longitude</span></th>
<th><span>Latitude</span></th>
<th><span>Place Name</span></th>
</tr>
</thead>
{placeFound.map((place) =>{
return (
<tbody>
<tr>
<td>{place.state}</td>
<td>{place.longitude}</td>
<td>{place.latitude}</td>
<td>{place["place name"]}</td>
</tr>
</tbody>
)})}
</table>
</div>)
}
}
React.useEffect(() => {
console.log("useEffect is run")
const query = encodeURIComponent(zipSearch);
axios
.get(`https://api.zippopotam.us/us/${query}`,{
})
.then((response) => {
setPlaceFound(response.data.places);
setErrorFound("");
})
.catch((ex) => {
let errorFound = axios.isCancel(ex)
? 'Request Cancelled'
: ex.code === 'ECONNABORTED'
? 'A timeout has occurred'
: ex.response.status === 404
? 'Resource Not Found'
: 'An unexpected error has occurred';
setErrorFound(ex.code);
setPlaceFound([]);
});
}
},[zipSearch]);
const search=(event: FormEvent<HTMLFormElement>) =>{
console.log("Search method runs")
event.preventDefault();
const form = event.target as HTMLFormElement;
const input = form.querySelector('#zipSearchInput') as HTMLInputElement;
setZipSearch(input.value);
}
return (
<div className="App">
<div className="search-container">
<h1>Place Search using Zip Code</h1>
<form className="searchForm" onSubmit={event => search(event)}>
<div>
<label htmlFor="zipSearchInput">Zip Code</label>
<input {...register('zipSearchInput', { required: true, minLength: 5, maxLength: 5 }) }
id="zipSearchInput"
name="zipSearchInput"
type="text"
/>
</div>
{
errors.zipSearchInput && <div className="error">Zip Code is required and must be 5 digits long</div>
}
<button type="submit">Search</button>
</form>
</div>
{placeFound.length !== 0 && renderPlaces()}
{errorFound !== "" && <p className="error">{errorFound}</p>}
</div>)
}
What you should probably do is actually use the library above react-hook-form and its functions.
Then I believe that the useEffect that you are using is pretty useless in this scenario, You are going the long way for a simpler task. You can simply call the api on submit of the form and get rid of the state zipSearch the react-hook-form is taking care of that for you.
Here's the fixed version of the code below:
import React, { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import axios from "axios";
interface IPlace {
state: string;
longitude: string;
latitude: string;
"place name": string;
}
export default function App2() {
const [placeFound, setPlaceFound] = useState<IPlace[]>([]);
const [errorFound, setErrorFound] = React.useState("");
const formMethods = useForm<{ zipSearchInput: string }>();
const renderPlaces = () => {
console.log("Render places runs");
if (placeFound.length !== 0) {
return (
<div className="table-container">
<table>
<thead>
<tr>
<th>
<span>State</span>
</th>
<th>
<span>Longitude</span>
</th>
<th>
<span>Latitude</span>
</th>
<th>
<span>Place Name</span>
</th>
</tr>
</thead>
{placeFound.map((place) => {
console.log(place);
return (
<tbody key={place.latitude}>
<tr>
<td>{place.state}</td>
<td>{place.longitude}</td>
<td>{place.latitude}</td>
<td>{place["place name"]}</td>
</tr>
</tbody>
);
})}
</table>
</div>
);
}
};
const search = (values: { zipSearchInput: string }) => {
console.log(values);
const query = encodeURIComponent(values.zipSearchInput);
axios
.get(`https://api.zippopotam.us/us/${query}`, {})
.then((response) => {
setPlaceFound(response.data.places);
setErrorFound("");
})
.catch((ex) => {
let _errorFound = axios.isCancel(ex)
? "Request Cancelled"
: ex.code === "ECONNABORTED"
? "A timeout has occurred"
: ex.response.status === 404
? "Resource Not Found"
: "An unexpected error has occurred";
setErrorFound(_errorFound);
setPlaceFound([]);
});
};
return (
<div className="App">
<div className="search-container">
<h1>Place Search using Zip Code</h1>
<FormProvider {...formMethods}>
<form className="searchForm" onSubmit={formMethods.handleSubmit(search)}>
<div>
<label htmlFor="zipSearchInput">Zip Code</label>
<input
{...formMethods.register("zipSearchInput", {
required: true,
minLength: 5,
maxLength: 5
})}
id="zipSearchInput"
name="zipSearchInput"
type="text"
/>
</div>
<button type="submit">Search</button>
</form>
</FormProvider>
</div>
{placeFound.length !== 0 && renderPlaces()}
{errorFound !== "" && <p className="error">{errorFound}</p>}
</div>
);
}
Good Luck
I create a table I get data from the database using the backend I just want to show the output of the table on the page the output will not be visible
This is the code of my table.js
//import Data from './TextForm';
function Table(props) {
console.log('type ', typeof (props.Data));
console.log('data ', props.Data)
return (
<table>
<thead>
<tr>
<th>Text No</th>
<th>TextArea</th>
</tr>
</thead>
<tbody>
{props.Data ?
Object.entries(props.Data).map((key,value)=> {
console.log('Key',key);
{
<tr key={value}>
<td>{key.textId}</td>
<td>{key.textArea}</td>
</tr>
}
})
: null
}
</tbody>
</table>
)
}
export default Table;
this is props. data where I get the data and define prop. data I get data from the backend I connected the frontend for getting and storing data
Edit
function TextForm(props) {
const [text, setText] = useState('');
const [submittext,setsubmittext]=useState(null);
const [Data,setData]=useState([]);
const handleOnClickUpperCase = () => {
var newText = text.toUpperCase();
setText(newText);
}
const handleOnClickLowerCase = () => {
var newText = text.toLowerCase();
setText(newText);
}
const handleOnChange = (e) => {
setText(e.target.value);
}
const handleOnPreview = (e) => {
e.preventDefault();
setsubmittext(text);
// console.log(text);
const ROOT_URL='https://localhost:7113/';
var formData={
Textarea:text
}
axios.post(`${ROOT_URL}api/demo-text`, formData, {
headers: {"Access-Control-Allow-Origin": "*", 'Content-Type': 'application/json'}
})
.then(function (response) {
console.log('successs')
//handle success
setData(response);
console.log('response ',Data);
})
.catch(function (response) {
console.log('error')
//handle error
console.log(response);
})
}
return (
<>
<div className="container">
<h1>{props.title}</h1>
<p>Enter Text Here:</p>
<div className="mb-3">
<textarea className="form-control" value={text} onChange={handleOnChange} id="mybox" rows="8"></textarea>
</div>
<Table Data={Data} />
{text === text.toLowerCase() ? <button className="btn btn-primary" onClick={handleOnClickUpperCase}>Convert to Upper Case</button> : <button className="btn btn-primary" onClick={handleOnClickLowerCase}>Convert to Lower Case</button>}
<button className="btn btn-primary mx-3" onClick={handleOnPreview}>submit</button>
</div>
<hr></hr>
<div className="container my-4" >
<h1>{props.sum}</h1>
<p>Text Word {text.split(" ").length} and Character is {text.length}</p>
<p>{0.008 * text.split(" ").length} Minutes to Read</p>
</div>
<hr></hr>
<div className="container my-4">
<h2>Preview Your Text</h2>
<p>{submittext}</p>
</div>
</>
)
}
the output of prop.data
here have iterator objects array so pls try the following the code see I created an example as your same array object as you shared img pls find an example here link.
import React from "react";
import ReactDOM from "react-dom";
const Data = {
data: {
result: [
{ textId: 1, textarea: "test" },
{ textId: 2, textarea: "1234" },
{ textId: 3, textarea: null },
{ textId: 4, textarea: null },
]
}
};
function Table(props) {
console.log("type ", typeof props.Data);
console.log("data ", props.Data);
return (
<table>
<thead>
<tr>
<th>Text No</th>
<th>TextArea</th>
</tr>
</thead>
<tbody>
{props?.Data?.data?.result?.map((item) => (
<>
<tr key={item.textId}>
<td>{item.textId}</td>
<td>{item.textarea}</td>
</tr>
</>
))}
</tbody>
</table>
);
}
function App() {
return (
<>
<Table Data={Data} />
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
So you have to iterate over props.Data.data.result instead of props.Data, You don't need the Object.entries(),
and make the initial value of the Data = {} instead of [].
const [Data, setData] = useState({});
In the conditional JSX code, you need to check if the object has keys or not, and you don't need Object.entries method, just go with props.Data.data.result.map(), Also you have to return tr from the map method as #Usama mentioned.
{Object.keys(props.Data).length ?
props.Data.data.result.map((key,value)=> {
return (<tr key={value}>
<td>{key.textId}</td>
<td>{key.textArea}</td>
</tr>)
})
: null
}
I have coded this so how to fetch the data as I have added social logins and phone so I have query where data gets stored in reactjs and how I have to fetch data I want e-mail and phone number and name when somebody login so that I can integrate apis
import React, { Component } from "react"
import firebase from "firebase"
import StyledFirebaseAuth from "react-firebaseui/StyledFirebaseAuth"
firebase.initializeApp({
apiKey: "AIzaSyBPqAebQzVeT5Kbd57TsDwoF_l3pRCVomY",
authDomain: "login-project-d33bf.firebaseapp.com"
})
class SocialLogin extends Component {
state = { isSignedIn: false }
uiConfig = {
signInFlow: "popup",
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.FacebookAuthProvider.PROVIDER_ID,
firebase.auth.PhoneAuthProvider.PHONE_SIGN_IN_METHOD,
],
callbacks: {
signInSuccess: () => false
}
}
componentDidMount = () => {
firebase.auth().onAuthStateChanged(user => {
this.setState({ isSignedIn: !!user })
console.log("user", user)
firebase.database().ref("signInSuccess").on("value", snapshot => {
let signInSuccess = [];
snapshot.forEach(snap => {
signInSuccess.push(snap.val());
})
this.setState({signInSuccess : signInSuccess})
})
})
}
render() {
return (
<div className="App">
{this.state.isSignedIn ? (
<span>
<div>Signed In!</div>
<button onClick={() => firebase.auth().signOut()}>Sign out!</button>
<h1>Welcome {firebase.auth().currentUser.displayName}</h1>
<img
alt="profile picture"
src={firebase.auth().currentUser.photoURL}
/>
</span>
) : (
<StyledFirebaseAuth
uiConfig={this.uiConfig}
firebaseAuth={firebase.auth()}
/>
)}
{this.state.signInSuccess.map(data =>{
return (
<tr>
<td>{data.email}</td>
<td>{data.mobilenumber}</td>
</tr>
)
})}
</div>
)
}
}
export default SocialLogin
so where does the data get store because I have to fetch the data from the code so I have to fetch data useremail and phonenumber and name so how can I do it?
It looks like the error is occurring because this.state.signInSuccess is not an array. May be good to check if this.state.signInSuccess is an array with data prior to perform the .map
render() {
const { signInSuccess, isSignedIn } = this.state;
const isSignInSuccess = Array.isArray(signInSuccess);
return (
<div className="App">
{isSignedIn ? (
<span>
<div>Signed In!</div>
<button onClick={() => firebase.auth().signOut()}>Sign out!</button>
<h1>Welcome {firebase.auth().currentUser.displayName}</h1>
<img
alt="profile picture"
src={firebase.auth().currentUser.photoURL}
/>
</span>
) : (
<StyledFirebaseAuth
uiConfig={this.uiConfig}
firebaseAuth={firebase.auth()}
/>
)}
{isSignInSuccess && isSignInSuccess.map(data =>{
return (
<tr>
<td>{data.email}</td>
<td>{data.mobilenumber}</td>
</tr>
)
})}
</div>
)
}
I'm trying to pass method ref to functional component but somehow it doesn't work
Here is the function:
import { FaTrashAlt } from 'react-icons/fa';
const ArticlesData = props => {
return(
props.products.map(product => {
return (
<tr>
<td>{product.name}</td>
<td>{product.description}</td>
<td>{product.price}$</td>
<td>
<span className="removeProduct--Container" onClick={props.click}>
<FaTrashAlt className="remove--Icon" />
</span>
</td>
</tr>
)
}).reverse()
)
}
export default ArticlesData;
Here is the request I'm trying to pass:
onRemove = (id) => {
fetch(`http://localhost:5000/products/:${id}/delete`, {
method: 'POST'
})
}
And here is how I pass:
<ArticlesData products={this.state.products} click={this.onRemove}/>
Update:
controller:
router.post('/:id/delete', (req, res) => {
try {
console.log(req.params.id)
productService.deleteOne(req.params.id)
res.status(200)
} catch (error) {
console.log(error)
}
})
service:
function deleteOne(id) {
return Product.deleteOne({_id: id});
}
You need to call the function with parameter id.
I'm assuming your product object has id attribute:
import { FaTrashAlt } from 'react-icons/fa';
const ArticlesData = props => {
return(
props.products.map(product => {
return (
<tr>
<td>{product.name}</td>
<td>{product.description}</td>
<td>{product.price}$</td>
<td>
<span className="removeProduct--Container" onClick={() => props.click(product.id)}>
<FaTrashAlt className="remove--Icon" />
</span>
</td>
</tr>
)
}).reverse()
)
}
export default ArticlesData;
Change ArticlesData component's code
from onClick={props.click}
to onClick={() => props.click(product.id)}
Full code:
import React from "react";
import ArticlesData from "./ArticlesData";
export default class SomeName extends React.Component {
onRemove = (id) => {
console.log(id);
fetch(`http://localhost:5000/products/:${id}/delete`, {
method: 'POST'
})
};
render() {
return (
<>
<ArticlesData click={this.onRemove} />
</>
);
}
}
import { FaTrashAlt } from 'react-icons/fa';
const ArticlesData = props => {
return(
props.products.map(product => {
return (
<tr>
<td>{product.name}</td>
<td>{product.description}</td>
<td>{product.price}$</td>
<td>
<span className="removeProduct--Container" onClick={() => props.click(product.id)}>
<FaTrashAlt className="remove--Icon" />
</span>
</td>
</tr>
)
}).reverse()
)
}
export default ArticlesData;
CodeSandbox Demo
I am writing a simple react page that renders 2 different html tables based off of which button is clicked on the screen. The issue I am having is that the table that is rendered for each button click is associated with the previous button click. (E.G. if I click button 1 one time then click button 2 the table associated with button 1 will be displayed.)
I am new to react so in order to get the tables to update I refactored my code to hold as much of the state as possible in the App.js class, I created the toggleState callback to associate the button clicks with state change of the parent, and I then pass that to DataProvider via the endpoint property. I realize this is probably where the state / UI disconnect is occurring, but I'm uncertain of the cause since I'm adhering to react principles to the best of my capability.
my class structure is as follows:
App
/ \
/ \
/ \
DataProvider ButtonToggle
|
Table
If it is relevant the table class is building the table based off of an API call, I will add the code for this, but it is not causing me problems so I do not believe it to be the source of the issue.
App.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import DataProvider from "./DataProvider";
import Table from "./Table";
import ButtonToggle from "./ButtonToggle";
class App extends Component {
constructor(props){
super(props);
this.state = {
input : 'employees',
endpoint : "api/employees/"
};
console.log("constructor app: " + this.state.input + "\n" + this.state.endpoint);
}
toggleState(input) {
if(input == "employees") {
this.setState({input : input, endpoint: "api/employees/"});
}
else {
this.setState({input : input, endpoint: "api/categories/"});
}
console.log("toggleState " + this.state.input + "\n" + this.state.endpoint);
}
render() {
return (
<div className="col-lg-12 grid-margin">
<div className="card">
<div className="card-title">
<div className="row align-items-center justify-content-center">
<div className="col-3"></div>
<div className="col-6">
<h1> Striped Table</h1>
</div>
<div className="col-3"></div>
</div>
<ButtonToggle toggleInput={ (input) => this.toggleState(input)}/>
</div>
<div className="card">
<div className="card-title"></div>
<div className="card-body">
<DataProvider endpoint={this.state.endpoint}
render={data => <Table data={data} />} />
</div>
</div>
</div>
</div>
);
}
}
export default App;
DataProvider.js
class DataProvider extends Component {
static propTypes = {
endpoint: PropTypes.string.isRequired,
render: PropTypes.func.isRequired
};
constructor(props) {
super(props);
this.state = {
data: [],
loaded: false,
placeholder: "Loading..."
};
}
componentWillReceiveProps(props) {
console.log("dataprov: " + this.props.endpoint);
this.componentDidMount();
}
componentDidMount() {
fetch(this.props.endpoint)
.then(response => {
if (response.status !== 200) {
return this.setState({ placeholder: "Something went wrong" });
}
return response.json();
})
.then(data => this.setState({ data: data, loaded: true }));
}
render() {
const { data, loaded, placeholder } = this.state;
return loaded ? this.props.render(data) : <p>{placeholder}</p>;
}
}
export default DataProvider;
ButtonToggle.js
class ButtonToggle extends Component {
constructor (props) {
super(props);
}
render() {
return (
<div className="row align-items-center justify-content-center">
<div className="col-3 center-in-div">
<button type="button" className="btn btn-info btn-fw" onClick={this.props.toggleInput.bind(this, 'categories')}> Categories </button>
</div>
<div className="col-3 center-in-div">
<button type="button" className="btn btn-info btn-fw" onClick={this.props.toggleInput.bind(this, 'employees')}>
Employees
</button>
</div>
<div className="col-6"></div>
</div>
);
}
}
export default ButtonToggle;
Table.js : I don't think this is a problem, but I may stand corrected.
import React from "react";
import PropTypes from "prop-types";
import key from "weak-key";
const Table = ({ data }) =>
!data.length ? (
<p>Nothing to show. Records: {data.length} </p>
) : (
<div className="table-responsive">
<h2 className="subtitle">
Showing <strong>{data.length} items</strong>
</h2>
<table className="table table-hover">
<thead>
<tr>
{Object.entries(data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</table>
</div>
);
Table.propTypes = {
data: PropTypes.array.isRequired
};
export default Table;
Below is the minimum working code I could come up with. Your Button and Table components can be dumb components which will get data from parent component and will present it.
Your Parent or container component will have logic to set the properties for Button and Table component.
As Table and Button components are dumb you can go with functional components.
I have added the code for calling api (I have tried to mimic the api call) and getting data in same parent component, you can separate it out.
You can work on style and validations as per your needs.
Let me know if you need any further help.
class ParentComponent extends Component {
constructor() {
super();
this.state = {
name: "Category"
}
this.onBtnClick = this.onBtnClick.bind(this);
}
componentDidMount() {
this.getData(this.state.name)
}
getData(name) {
if (name === "Category") {
this.apiCall("/Category").then((data) => {
this.setState({ data: data })
})
} else {
this.apiCall("/Employee").then((data) => {
this.setState({ data: data })
})
}
}
apiCall(url) {
return new Promise((res, rej) => {
setTimeout(() => {
if (url === "/Employee") {
res([{ "Emp Name": "AAA", "Emp Age": "20" }, { "Emp Name": "BBB", "Emp Age": "40" }])
} else {
res([{ "Cat Id": "XXX", "Cat Name": "YYY" }, { "Cat Id": "MMM", "Cat Name": "NNN" }])
}
}, 1000)
});
}
onBtnClick(name) {
let newName = "Category"
if (name === newName) {
newName = "Employee"
}
this.setState({ name: newName, data: [] }, () => {
this.getData(newName);
})
}
render() {
return (<>
<ButtonComponent name={this.state.name} onBtnClick={this.onBtnClick}></ButtonComponent>
<TableComponent data={this.state.data} />
</>)
}
}
const ButtonComponent = ({ name, onBtnClick }) => {
return <Button onClick={() => { onBtnClick(name) }}>{name}</Button>
}
const TableComponent = ({ data }) => {
function getTable(data) {
return < table >
<thead>
<tr>
{getHeading(data)}
</tr>
</thead>
<tbody>
{getRows(data)}
</tbody>
</table >
}
function getHeading(data) {
return Object.entries(data[0]).map((key) => {
return <th key={key}>{key[0]}</th>
});
}
function getRows(data) {
return data.map((row, index) => {
return <tr key={"tr" + index}>
{Object.entries(data[0]).map((key, index) => {
console.log(row[key[0]]);
return <td key={"td" + index}>{row[key[0]]}</td>
})}
</tr>
})
}
return (
data && data.length > 0 ?
getTable(data)
: <div>Loading....</div>
)
}