I am in the process of learning graphql and react-apollo. I have set up a search query in my code. I am unsure how to pass a variable from my code (i.e. this.state.search) to my grapnql call.
I have looked at many answers including this one, but it seems a bit different.
The docs also don't seem to give any guidance on how to use state as the variable.
My code is below.
Can anyone advise how to connect both of these?
import React, { Component} from 'react'
import { graphql } from 'react-apollo'
import gql from 'graphql-tag'
class Search extends Component {
constructor(props) {
super(props)
this.state = {
search: ''
}
}
updateSearch = (e) => {
this.setState({
search: e.target.value
})
}
submitSearch = (e) => {
e.preventDefault()
console.log(this.state)
}
render() {
const { search } = this.state;
return (
<form onSubmit={ this.submitSearch }>
<input
type='text'
onChange={ this.updateSearch }
value={ search }
placeholder='Search'
/>
</form>
)
}
}
export default graphql(gql`
{
search(query: "Manchester", type: TEAM) {
name
}
}`)(Search)
You'll want to split this up into at least two components. One that holds the state of what the user searched, then another that actually does the querying by getting a prop. Additionally you can have the apollo higher order component skip the query if the form was submitted without entering something.
import React, {Component} from 'react'
import {graphql} from 'react-apollo'
import gql from 'graphql-tag'
class Results extends Component {
render() {
// apollo provides results under the data prop
const {data} = this.props;
return <h1>{data.search.namej}</h1>
}
}
const ResultsWithQuery = graphql(gql`
query FindTeam($query: String!) {
search(query: $query, type: TEAM) {
name
}
}
`, {skip: (ownProps) => !ownProps.query})(Results);
export class Search extends Component {
constructor(props) {
super(props)
this.state = {
search: ''
}
}
updateSearch = (e) => {
this.setState({
search: e.target.value
})
}
submitSearch = (e) => {
e.preventDefault()
console.log(this.state)
}
render() {
const {search} = this.state;
return (
<div>
<form onSubmit={this.submitSearch}>
<input
type='text'
onChange={this.updateSearch}
value={search}
placeholder='Search'
/>
<ResultsWithQuery query={search} />
</form>
</div>
)
}
}
* UPDATE *
Now that react-apollo#2.1 has been released there is an alternative way using render props.
https://www.apollographql.com/docs/react/essentials/get-started.html#request
This simplifies the number of components you need in this case.
import React, { Component} from 'react'
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
const SearchQuery = gql`
query FindTeam($query: String!) {
search(query: $query, type: TEAM) {
name
}
}
`;
export default class Search extends Component {
constructor(props) {
super(props)
this.state = {
search: ''
}
}
updateSearch = (e) => {
this.setState({
search: e.target.value
})
}
submitSearch = (e) => {
e.preventDefault()
console.log(this.state)
}
render() {
const { search } = this.state;
return (
<form onSubmit={ this.submitSearch }>
<input
type='text'
onChange={ this.updateSearch }
value={ search }
placeholder='Search'
/>
<Query query={SearchQuery} skip={!search} variables={{query: search}}>
{({loading, error, data}) => {
if (loading) return null;
if (error) throw err;
return <h1>{data.search.namej}</h1>
}}
</Query>
</form>
)
}
}
Related
I have created search box in react where data is searchable form wordpress post api. i want to show this search results in separate page. from below code the page is redirecting to blank page
search.js
import axios from 'axios';
import React, { Component } from 'react';
import { Button } from 'react-bootstrap';
import { Redirect } from '#reach/router';
class Search extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
term: '',
};
this.submit = this.submit.bind(this);
this.changeTerm = this.changeTerm.bind(this);
}
changeTerm(event) {
this.setState({ term: event.target.value });
}
submit(event) {
let url = 'https://example.com/wp-json/wp/v2/posts?_embed&search=' + encodeURI(this.state.term) + '&per_page=100';
axios.get(url)
.then(response => {
let data = {
results: response.data,
};
this.setState(data);
})
.catch(error => console.log(error));
}
render() {
return (
<div>
<form onSubmit={this.submit} action="search/results">
<input onChange={this.changeTerm} />
<Button type="submit" bsStyle="primary">Find</Button>
</form>
{this.state.results.length > 0 &&
<Redirect to={{
pathname: '/search/results',
state: { results: this.state.results }
}} />
}
</div>
);
}
}
export default Search;
result.js
So I'm trying to create a list app using React. I don't have any errors or warnings, and my input bar is showing, but my textList won't render. In other words when ever I hit enter, the text info I put in the input bar won't create a list as expected.
I tried using keyCode === 13 and onKeyDown, but it's not having any effect on the app. What am I missng?
Here's my code:
filtered-input.js
import React, { Component } from "react";
import "./filtered-input.css";
import ItemList from "./item-list";
export class FilteredInput extends Component {
constructor(props) {
super(props);
this.state = {
textList: [],
};
}
handleChange(e) {
this.setState({ value: e.target.value });
}
handleAdd = (e) => {
if (e.keyCode === 13) {
let lists = this.props.state.textList;
lists.push(this.props.state.value);
this.setState({ textList: lists });
}
};
render() {
console.log("Here comes state -->");
console.log(this.state);
return (
<div>
<input
className="filtered-input-box"
type="text"
onKeyDown={this.props.handleAdd}
value={this.state.value}
onChange={this.props.handleChange}
/>
<ItemList item={this.state.textList}></ItemList>
</div>
);
}
}
ItemList.js
import React, { Component } from 'react';
export class ItemList extends Component {
render() {
const items = this.props.item.map((item) =>
<li>{item}</li>
);
return (
<ul>
{items}
</ul>
);
}
}
export default ItemList;
App.js
import React, { Component } from 'react';
import { FilteredInput } from './filtered-input.js';
export class App extends Component {
render() {
return (
<div>
<p>This page demonstrates a component that maintains its own state.</p>
<FilteredInput />
</div>
);
}
}
There were small syntactical mistakes here and there, apart from that everything looks perfect:
import React, { Component } from "react";
import "./style.css";
export default class App extends Component {
render() {
return (
<div>
<p>This page demonstrates a component that maintains its own state.</p>
<FilteredInput />
</div>
);
}
}
class FilteredInput extends Component {
constructor(props) {
super(props);
this.state = {
textList: [],
value: ""
};
}
handleChange = e => {
console.log(e.target.value);
this.setState({ value: e.target.value });
};
handleAdd = e => {
console.log("hi");
if (e.keyCode === 13) {
let lists = this.state.textList;
lists.push(this.state.value);
this.setState({ textList: lists });
}
};
render() {
console.log("Here comes state -->");
console.log(this.state);
return (
<div>
<input
className="filtered-input-box"
type="text"
onKeyDown={this.handleAdd}
value={this.state.value}
onChange={this.handleChange}
/>
<ItemList item={this.state.textList} />
</div>
);
}
}
class ItemList extends Component {
render() {
const items = this.props.item?.map(item => <li>{item}</li>);
return <ul>{items}</ul>;
}
}
Full working app : Stackblitz
I'm a bit new to React and Firestore and already trying to figure out what is happening for a couple of hours. I Try to make my filter function working with data which I receive from Firestore in APP.js. I pass the data {tasks, searchTerm} to DASHBOARD component. The filter worked before when using state and props, but after replacing the hard-coded data in state with firestore data, it doesn't work anymore and I get the following error when filtering the array in the DASHBOARD component:
Cannot read property 'toLowerCase' of undefined
I've tried to send the data without any filtering directly to TASKS.js and this is working correctly (all the tasks are shown). But as soon as I pass newArray to , it doesn't work anymore.
Also, when logging task.title in tasks.filter function in the DASHBOARD component, it shows all the data (with a little delay because the data is coming from Firestore)
APP.JS -
import React, { Component } from 'react';
import './App.css';
import Dashboard from './Components/Dashboard/Dashboard'
import AddTask from './Components/Tasks/Task/AddTask'
import Navbar from './Components/Navbar/Navbar'
import Searchbar from './Components/Searchbar/Searchbar'
import firebase from './Firebase';
class App extends Component {
constructor(props) {
super(props)
this.ref = firebase.firestore().collection('tasks')
this.state = {
tasks: [],
searchTerm: ""
}
this.handleLikeButton = this.handleLikeButton.bind(this)
this.handleRemoveButton = this.handleRemoveButton.bind(this)
this.addTask = this.addTask.bind(this)
this.handleFilter = this.handleFilter.bind(this)
}
componentWillMount() {
const db = firebase.firestore()
const allTasks = []
db.collection('tasks').onSnapshot(collection => {
const tasks = collection .docs.map(doc => doc.data())
this.setState({ tasks: tasks, searchTerm: "" })
})
}
handleLikeButton = (task) => (e) => {
const tasks = [...this.state.tasks]
const index = tasks.indexOf(task)
tasks[index].likes++
this.setState({
tasks: tasks
})
}
addTask = (taskName) => (e) => {
this.ref.add({
id: Math.floor(Math.random() * 100000000000000),
title: taskName,
likes: 0
})
}
handleRemoveButton = (removingTask) => (e) => {
const tasks = [...this.state.tasks]
const newTasks = tasks.filter(task => removingTask.id !== task.id)
this.setState({
tasks: newTasks
})
}
handleFilter = (searchTerm) => {
this.setState({
searchTerm: searchTerm
})
}
render() {
return (
<div className="App">
<Navbar />
<Searchbar handleFilter={this.handleFilter} />
<AddTask addTask={this.addTask} />
<Dashboard tasks={this.state.tasks} searchTerm={this.state.searchTerm} handleLikeButton={this.handleLikeButton} handleRemoveButton={this.handleRemoveButton}/>
</div>
);
}
}
export default App;
DASHBOARD.JS -
import React, { Component } from 'react'
import Tasks from '../Tasks/Tasks'
class Dashboard extends Component {
constructor(props) {
super(props)
this.filterTasks = this.filterTasks.bind(this)
}
filterTasks = () => {
const tasks = [...this.props.tasks]
const newArray = tasks.filter(task =>
task.title.toLowerCase().indexOf(this.props.searchTerm.toLowerCase()) > -1)
return (
<Tasks tasks={newArray} handleLikeButton={this.props.handleLikeButton} handleRemoveButton={this.props.handleRemoveButton} />
)
}
render() {
return (
<div>
<h2>Dashboard</h2>
{this.filterTasks()}
</div>
)
}
}
export default Dashboard
ADDTASK.JS
import React, { Component } from 'react'
class AddTask extends Component {
constructor(props) {
super(props)
this.state = {
addNewTaskFieldEmpty: true,
taskName: ""
}
this.onChangeHandler = this.onChangeHandler.bind(this)
this.disableButton = this.disableButton.bind(this)
}
onChangeHandler(e) {
this.setState({
taskName: e.target.value,
})
this.disableButton(e.target.value)
}
disableButton(taskName) {
if(taskName.length == 0) {
this.setState({addNewTaskFieldEmpty: true})
} else {
this.setState({addNewTaskFieldEmpty: false})
}
}
render() {
return (
<div>
<div className="mdc-text-field half-size">
<input className="mdc-text-field__input " onChange={this.onChangeHandler} />
<div className="mdc-line-ripple"></div>
<label className="mdc-floating-label">Task Name</label>
</div>
<a className={"btn-floating btn-large waves-effect waves-light red " + (this.state.addNewTaskFieldEmpty ? 'disabled' : '')} onClick={this.props.addTask(this.state.taskName)}><i className="material-icons">add</i></a>
</div>
)
}
}
export default AddTask
Lint your App.css for any errors.
I encountered this message. I traced it to a CSS include:
.box-table { border-color:; border: 1px solid #dbdad8; }
The missing value of border-color: caused npm run build to fail.
Interestingly, the same file contained
.submenu-button.submenu-opened:after { background:; }
which caused no problems at all.
After get the comments array from post component and pass it to comments component
the logs start to show the error in the screenshot below
the components are:
import React, { Component } from "react";
import axios from "axios";
import Comments from "../components/comments";
class Article extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
error: "",
comment: ""
};
}
componentDidMount() {
this.getComments();
}
getComments = () => {
const {
match: { params }
} = this.props;
return axios
.get(`/articles/${params.id}/comments`, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
}
})
.then(response => {
return response.json();
})
.then(response => this.setState({ comments: response.comments }))
.catch(error =>
this.setState({
error
})
);
};
render() {
return (
<div>
{this.state.title}
<div>
<h2>Comments</h2>
<Comments
getComments={this.getComments}
/>
</div>
</div>
);
}
}
export default Article;
and Comments component
import React, { Component } from "react";
import PropTypes from "prop-types";
import Comment from "./comment";
import axios from "axios";
import Article from "../screens/article";
class Comments extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
comment: "",
error: ""
};
this.load = this.load.bind(this);
this.comment = this.comment.bind(this);
}
componentDidMount() {
this.load();
}
load() {
return this.props.getComments().then(comments => {
this.setState({ comments });
return comments;
});
}
comment() {
return this.props.submitComment().then(comment => {
this.setState({ comment }).then(this.load);
});
}
render() {
const { comments } = this.state;
return (
<div>
{comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
);
}
}
export default Comments;
so, I've tried to pass it by props, and set the state on comments component.
and instead of use just comments.map I've tried to use this.state but show the same error in the logs.
So, someone please would like to clarify this kind of issue?
seems pretty usual issue when working with react.
If an error occurs you do:
.catch(error => this.setState({ error }) );
which makes the chained promise resolve to undefined and that is used as comments in the Comments state. So you have to return an array from the catch:
.catch(error => {
this.setState({ error });
return [];
});
Additionally it woupd make sense to not render the Comments child at all if the parents state contains an error.
The other way is checking whether it’s an array and if so check it’s length and then do .map. You have initialized comments to empty array so we don’t need to check whether it’s an array but to be on safer side if api response receives an object then it will set object to comments so in that case comments.length won’t work so it’s good to check whether it’s an array or not.
Below change would work
<div>
{Array.isArray(comments) && comments.length>0 && comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
The first time the comments component renders there was no response yet so comments were undefined.
import React, { Component } from "react";
import PropTypes from "prop-types";
import Comment from "./comment";
import axios from "axios";
import Article from "../screens/article";
class Comments extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
comment: "",
error: ""
};
this.load = this.load.bind(this);
this.comment = this.comment.bind(this);
}
componentDidMount() {
this.load();
}
load() {
return this.props.getComments().then(comments => {
this.setState({ comments });
return comments;
});
}
comment() {
return this.props.submitComment().then(comment => {
this.setState({ comment }).then(this.load);
});
}
render() {
const { comments } = this.state;
if (!comments) return <p>No comments Available</p>;
return (
<div>
{comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
);
}
}
export default Comments;
I want to pass values from SearchInput (parent) to FetchData (child) component. It does not work properly, because I have to click twice to fetch data and this.props.loaded should be true after click on submit button. I know, that I should use callback function, but I dont know, which function and where. I'm started to learn ReactJS a week ago.
import React, {Component} from "react";
import FetchData from "./FetchData";
export default class SearchInput extends Component {
constructor(props) {
super(props);
this.state = {
cityName: "",
loaded: false
}
this.handleCityNameChange = this
.handleCityNameChange
.bind(this);
this.handleSubmitButton = this
.handleSubmitButton
.bind(this);
}
handleCityNameChange = (e) => {
const val = e.target.value;
this.setState({cityName: val});
e.preventDefault();
}
handleSubmitButton = (e) => {
//const val = document.getElementById("search").value;
this.setState({cityName: this.state.cityName, loaded: true});
e.preventDefault();
}
render() {
const {cityName, loaded} = this.state;
return (
<div>
<form>
<label htmlFor="search">Search city:</label>
<input
type="text"
name="search"
id="search"
value={this.state.cityName}
onChange={this.handleCityNameChange}/>
<input type="submit" onClick={this.handleSubmitButton}/>
</form>
<FetchData
cityName={cityName}
loaded={loaded}
handleSubmitButton={this.handleSubmitButton}/>
</div>
)
}
}
import React, {Component} from "react";
import axios from "axios";
import DisplayWeather from "./DisplayWeather";
export default class FetchData extends Component {
constructor(props) {
super(props);
this.state = {
descriptionMain: "",
description: "",
temperature: null,
weatherIcon: ""
}
}
// otherFunc() {
// this.props.handleSubmitButton();
// }
fetchData = () => {
if (this.props.loaded) {
const apiURL = `https://api.openweathermap.org/data/2.5/weather?q=${this.props.cityName}&units=metric&APPID=e6f4d816d3ade705ec1d8d9701b61e14`;
console.log(apiURL)
axios
.get(apiURL)
.then(res => {
this.setState({descriptionMain: res.data.weather[0].main, description: res.data.weather[0].description, temperature: res.data.main.temp, weatherIcon: res.data.weather[0].icon});
})
}
}
componentWillReceiveProps() {
this.fetchData();
}
render() {
return (
<div>
<DisplayWeather {...this.state} cityName={this.props.cityName}/>
</div>
)
}
}
export default class DisplayWeather extends Component {
render() {
const {descriptionMain, description, temperature, weatherIcon, cityName} = this.props;
return (
<div>
<h3>{cityName}</h3>
<h4>Sky: {description}</h4>
<h5>Description: {descriptionMain}</h5>
<span className="temperature">{temperature}
°C</span>
<img
src={`http://openweathermap.org/img/w/${weatherIcon}.png`}
alt={`${description}`}/>
</div>
)
}
}
<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>
You have to enclose the submit attribute in an arrow function:
onClick{()=>this.handleSubmutButton()}
Do the same for on change as well