I have been trying to use multiple graphql() enhancers at once, but compose seems not to be working. I have tried a load of different imports from different libraries, still nothing. Does any know any fix? The data is not being passed from graphl() to my component props, so I'm getting an error that reads Cannot destructure property 'loading' of 'data' as it is undefined.
Here's my component:
import React,{useState} from 'react'
// import {compose} from 'redux'
import { flowRight as compose } from 'lodash'
import { graphql} from 'react-apollo'
import {getAuthorsQuery, addBookMutation} from './queries/query'
const AddBook = (props) => {
const [formData, setFormData] = useState({
name:'',
genre:'',
authorId:''
})
const {name, genre, authorId} = formData
const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value})
const displayAuthors = () => {
let data = props.data
const {loading} = data
if(loading){
return (<option>Loading Authors</option>)
} else{
return data.authors.map(author => {
return (<option key={author.id} value={author.id}>{author.name}</option>)
})
}
}
const submitForm = e => {
e.preventDefault();
console.log(formData);
}
return (
<form id="add-book" onSubmit={e => submitForm(e)}>
<div className="field">
<label>Book name:</label>
<input
type="text"
onChange={e => onChange(e)}
name="name"
value={name}
/>
</div>
<div className="field">
<label>Genre:</label>
<input
name="genre"
value={genre}
type="text"
onChange={e => onChange(e)}
/>
</div>
<div className="field">
<label>Author:</label>
<select
name="authorId"
value={authorId}
onChange={e => onChange(e)}
>
<option>Select Author</option>
{displayAuthors()}
</select>
</div>
<button>+</button>
</form>
)
}
// export default graphql(getAuthorsQuery)(AddBook)
export default compose(
graphql(getAuthorsQuery, {name: "getAuthorsQuery"}),
graphql(addBookMutation, {name:"addBookMutation"})
)(AddBook)
And my query:
import { gql } from 'apollo-boost'
const getBooksQuery = gql`
{
books{
name,
id
}
}
`
const getAuthorsQuery = gql`
{
authors{
name,
id
}
}
`
const addBookMutation = gql`
mutation{
addBook(name:"", genre:"", authorId:""){
name,
id
}
}
`
export {getAuthorsQuery, getBooksQuery, addBookMutation}
You're providing a name option for both your HOCs, so the data will be available under those prop names:
props.getAuthorsQuery.loading
That said, the HOCs are deprecated -- you should probably be using the newer hooks API.
You can make use of useQuery hook from apollo and execute the queries like
import { useQuery } from '#apollo/react-hooks';
import React,{useState} from 'react'
// import {compose} from 'redux'
import { flowRight as compose } from 'lodash'
import {getAuthorsQuery, addBookMutation} from './queries/query'
const AddBook = (props) => {
const [formData, setFormData] = useState({
name:'',
genre:'',
authorId:''
})
const authors = useQuery(getAuthorsQuery):
const bookMutation = useQuery(addBookMutation);
... rest of code here
}
export default AddBook;
I was dealing with the same issue. I got useQuery and useMutation methods to work together and did not have to use compose. I am still new to gql so if you spot any bad practice or a tip, I would love to hear:
const authorsData = useQuery(authorsQuery)
//addBook takes args and send data to db when triggered in submitHandler
const [addBook] = useMutation(addBookMutation);
const submitHandler = (e) => {
e.preventDefault();
console.log(name, genre, author)
addBook({
variables: {
name: name,
genre: genre,
authorId: author
},
refetchQueries: [{ query: bookList }]
})
}
Related
react_devtools_backend.js:4026 Error adding document:
SyntheticBaseEvent {_reactName: 'onSubmit', _targetInst: null, type: 'submit', nativeEvent: SubmitEvent, target: form, …}
This is todo app and what im trying to do is to send object of 'title' and 'completed' in firestore in collection todos
Something goes wrong :(
import React, {useState} from 'react'
import { db } from '../firebase/firebase'
import { collection, addDoc } from 'firebase/firestore'
const AddTodo = () => {
const [title, setTitle] = useState('')
const submitHandler = async e => {
e.preventDefault()
try {
await addDoc(collection(db, 'todos'), {
title,
completed: false
})
} catch (error) {
console.error('Error adding document: ', e)
}
}
return (
<form onSubmit={submitHandler}>
<input
type='text'
placeholder='Enter todo...'
value={title}
onChange={e => setTitle(e.target.value)}
/>
<button type='submit'>Add</button>
</form>
)
}
export default AddTodo
Firebase db
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'
const firebaseConfig = {
//pasted from project settings
}
const app = initializeApp(firebaseConfig)
const db = getFirestore(app)
export { db }
Hi i am new developer testing platform. I have a problem but I did not find a solution or work it with correct way. I am trying to login component test with to parameter by Inputs. Firstly I filled these are userEvent.type. After I am clicking my button. And when I was waiting my method that call by onSubmitForTest in one time , I am facing an error like fallowing image.
What is the reason of this ? How can I solve my problem ? Thanks for your helps.
My Login.tsx component:
import React, { FC, useState } from "react";
import { useTranslation } from "react-i18next";
import Input from "../../components/Input";
import InputPassword from "../../components/Input/InputPassword";
import ButtonLoading from "../../components/Button/ButtonLoading";
import { GetLoginInfo, ILoginRequest } from "../../store/actions/loginActions";
interface ILoginState {
emailorUsername: string;
password: string;
}
const initialState = {
emailorUsername: "",
password: "",
};
interface IProps {
onSubmitForTest: (items: any) => void
}
const Login: FC<IProps> = ({ onSubmitForTest }) => {
const { t } = useTranslation();
const [state, setstate] = useState<ILoginState>(initialState);
const onChange = (key: string, value: string | number) => {
setstate({ ...state, [key]: value });
};
const handleLogin = async () => {
const loginRequest: ILoginRequest = {
emailOrUsername: state.emailorUsername,
password: state.password,
returnUrl: "",
};
const response = await GetLoginInfo(loginRequest);
if (response.isSucceed) { } else { }
};
const renderLoginPart = () => {
return (
<div className="flex">
<Input
name="emailorUsername"
label={t("emailorUsername")}
value={state.emailorUsername}
onChange={(val: any) => onChange("emailorUsername", val)}
/>
<InputPassword
name="password"
label={t("password")}
value={state.password}
onChange={(val: any) => onChange("password", val)}
/>
<ButtonLoading
text={t("login")}
variant="contained"
onClick={() => {
if (onSubmitForTest) {
const loginRequestItemForTest = {
emailOrUsername: "testUsername",
password: "testPassword",
};
onSubmitForTest(loginRequestItemForTest)
}
handleLogin()
}}
dataTestid={"login-button-element"}
/>
</div>
);
};
return <div className="">{renderLoginPart()}</div>;
};
export default Login;
My index.test.js :
import React from 'react'
import { render, screen, waitFor } from "#testing-library/react"
import LoginPage from "../index"
import userEvent from "#testing-library/user-event"
const onSubmit = jest.fn()
beforeEach(()=>{
const { } = render(<LoginPage />)
onSubmit.mockClear()
})
test('Login form parametre olarak doğru data gönderme testi', async () => {
const eMail = screen.getByTestId('text-input-element')
const password = screen.getByTestId('password-input-element')
userEvent.type(eMail, "fillWithTestUsername")
userEvent.type(password, "fillWithTestPassword")
userEvent.click(screen.getByTestId('login-button-element'))
await waitFor(()=>{
expect(onSubmit).toHaveBeenCalledTimes(1)
})
})
beforeEach(()=>{
render(<LoginPage onSubmitForTest={onSubmit} />)
})
Please try doing this in beforeEach. If this still doesn't work you can try replacing toHaveBeenCalledTimes with toBeCalledTimes like below
await waitFor(()=>{
expect(onSubmit).toBeCalledTimes(1)
})
I have a form that adds new articles. I need to create another form that triggers when I click on a created article and add a property "keyword" to the article state and display it. I tried to do something but I am kinda stuck.
Form.jsx component that adds the article/s:
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { v1 as uuidv1 } from 'uuid';
import { ADD_ARTICLE } from '../constants/action-types';
const Form = () => {
const [title, setTitle] = useState('');
const dispatch = useDispatch();
const handleChange = (e) => {
const { value } = e.target
setTitle(value);
}
const handleSubmit = (e) => {
e.preventDefault();
const id = uuidv1();
dispatch({ type: ADD_ARTICLE, payload: { id, title } });
setTitle('');
}
return (
<form onSubmit={handleSubmit}>
<div className='form-group'>
<label htmlFor='title'>Title</label>
<input
type='text'
className='form-control'
id='title'
value={title}
onChange={handleChange}
/>
</div>
<input className='btn btn-success btn-lg' type='submit' value='SAVE' />
</form>
);
}
export default Form;
List.jsx component where the articles are displayed:
import React, { useState,useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import KeywordForm from './KeywordForm.jsx';
import { fetchArticles } from '../thunk';
const List = () => {
const [showForm,setShowForm]=useState(false);
const articles = useSelector(state => state.articles);
const dispatch = useDispatch();
const displayForm=()=>{
setShowForm(!showForm)
}
useEffect(() => {
dispatch(fetchArticles);
}, []);
return (
<>
<ul className='list-group list-group-flush'>
{articles.map(article => (
<li className='list-group-item' key={article.id} onClick={displayForm}>
{article.title}
</li>
))}
</ul>
<div>
{showForm && (
<KeywordForm />
)}
</div>
</>
);
}
export default List;
Here i added a state that displays the KeywordForm component when I click an article.
KeywordForm.jsx component,this is the one that I created to add the keyword:
import React, { useState } from 'react';
import { useDispatch ,useSelector} from 'react-redux';
import { ADD_KEYWORD } from '../constants/action-types';
const KeywordForm = ({id,title}) => {
const [keyword,setKeyword]=useState('');
const articles = useSelector(state => state.articles);
const dispatch=useDispatch();
console.log(articles)
const handleChange = (e) => {
const { value } = e.target
setKeyword(value);
}
const handleSubmit = (e) => {
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<div className='form-group'>
<label htmlFor='keyword'>Keyword</label>
<input
type='text'
className='form-control'
id='keyword'
value={keyword}
onChange={handleChange}
/>
</div>
<input className='btn btn-success btn-lg' type='submit' value='SAVE' />
</form>
);
}
export default KeywordForm;
reducers.js
const initialState = {
articles: []
};
const rootReducer = (state = initialState, action) => {
const { type, payload } = action;
switch(type) {
case ADD_ARTICLE: {
return {...state,
articles: [...state.articles,payload]
};
}
case ADD_KEYWORD: {
return Object.assign({}, state, {
articles: state.articles.concat(payload)
});
}
case ARTICLES_RETRIEVED: {
return Object.assign({}, state, {
articles: state.articles.concat(payload)
});
}
default:
return state;
}
}
export default rootReducer;
actions.js
import { ADD_ARTICLE, ARTICLES_RETRIEVED,ADD_KEYWORD } from '../constants/action-types';
const addArticle = (payload) => {
return { type: ADD_ARTICLE, payload };
}
const addKeyword = (payload) => {
return { type: ADD_KEYWORD, payload };
}
const articlesRetrieved = (payload) => {
return { type: ARTICLES_RETRIEVED, payload };
}
export { addArticle, articlesRetrieved,addKeyword };
What should i add to my reducers/actions to make this work? My idea is that i have to somehow pass the id of the article clicked and then in the reducer find it's index or something and check it with the payload.id .
You want to modify an existing article in the state and a keyword to it (can there be an array of keywords, or just one?). In order to do that, your action payload will need to contain both the keyword and the id of the article that it belongs to.
Your reducer will find the article that matches the id and replace it with a copied version that has the keyword added to it.
case ADD_KEYWORD: {
return {
...state,
articles: state.articles.map(article =>
// find the article to update
article.id === payload.id ?
// update it
{ ...article, keyword: payload.keyword } :
// otherwise return the original
article
}
}
This is easier to do with the official Redux Toolkit because you can modify the draft state directly and you don't need to worry about mutations.
why "addTodo" in the "handleAdd" method gives me "method expression is not of function type" ?
import React, {useState} from 'react';
import useAddTodo from "../../hooks/todos/useAddTodo";
function Home() {
const [value, setValue] = useState('');
const addTodo = useAddTodo();
const handleChange = e => {
setValue(e.target.value)
};
const handleAdd = e => {
e.preventDefault();
const todo = {
title: value,
status: false,
};
addTodo({
variables: todo,
})
};
return (
<form onSubmit={handleAdd}>
<input onChange={handleChange} value={value} type="text" placeholder="todo title"/>
<input onClick={handleAdd} type="submit" value="add"/>
</form>
);
}
export default Home;
here is my hook
import {useMutation} from 'react-apollo-hooks'
import AddTodoMutation from "./graphql/mutations/AddTodoMutation";
function useAddTodo() {
return useMutation(AddTodoMutation);
}
export default useAddTodo
and my mutation
import gql from 'graphql-tag'
export const AddTodoMutation = gql`
mutation AddTodoMutation($title: String! $status: Boolean!) {
addTodo(title: $title status: $status) {
_id
title
status
}
}
`
export default AddTodoMutation
Can you guys explain to me what seems to be the problem? i would be appreciated!
According to the documentation, useMutation returns an array (not a single value).
For example:
const [addTodo, { data }] = useMutation(ADD_TODO);
However, it appears you're returning and invoking the entire array value.
To continue using your custom hook the way it is, try updating it as follows:
import {useMutation} from 'react-apollo-hooks'
import AddTodoMutation from "./graphql/mutations/AddTodoMutation";
function useAddTodo() {
// Destructure the addTodo mutation
const [addTodo] = useMutation(AddTodoMutation);
// Return the value to continue using it as is in the other files
return addTodo;
}
export default useAddTodo
hello guys i'm new to react and redux .when i click a submit button Form.jsx an will dispatch but it shows type property undefined.any idea.
This is the image link of my error
https://imgur.com/a/aby1bci
this my store code below
import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "../reducers/index";
import { forbiddenWordsMiddleware } from "../middleware";
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ||
compose;
const store = createStore(
rootReducer,
storeEnhancers(applyMiddleware(forbiddenWordsMiddleware))
);
export default store;
my action code below
import { ADD_ARTICLE } from "../constants/action-types";
export function addArticle(payload) {
return { type: ADD_ARTICLE, payload };
}
here is my Form component looks like below code
import React, { Component } from "react";
import { connect } from "react-redux";
import uuidv1 from "uuid";
import { addArticle } from "../actions/index";
function mapDispatchToProps(dispatch) {
return {
addArticle: article => dispatch(addArticle(article))
};
}
class ConnectedForm extends Component {
constructor() {
super();
this.state = {
title: ""
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ [event.target.id]: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
const { title } = this.state;
const id = uuidv1();
this.props.addArticle({ title, id });
this.setState({ title: "" });
}
render() {
const { title } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
value={title}
onChange={this.handleChange}
/>
</div>
<button type="submit" className="btn btn-success btn-lg">
SAVE
</button>
</form>
);
}
}
const Form = connect(
null,
mapDispatchToProps
)(ConnectedForm);
export default Form;
ADD_ARTICLE type should be in quotes like so:
// Action creator
export const addArticle = (article) => {
// returns an action
return {
type: 'ADD_ARTICLE',
payload: article
};
};
Notice how I implement the payload as well, you may want to take a look at that too.
Additionally, study and review ES6 syntax so you can avail yourself of the power of arrow functions and avoid having to use so many bind(this) and subsequently have cleaner code.
I think your mapDispatchToProps method is wrong. It must be something like below:
const mapDispatchToProps = {
addArticle
}