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
Related
I have React component like this:
import { useRecoilState} from 'recoil';
import { inputText } from './globalState';
export const Input = () => {
const [text, setText] = useRecoilState(inputText);
const handleOnChange = async (event) => {
setText(event.target.value)
console.log(text);
}
return (
<input value={text} onChange={handleOnChange} placeholder='blabla' />
)
}
Also I have global state for recoil purpose:
import { atom } from 'recoil';
export const inputText = atom({
key: 'input',
default: ''
})
My question is, why console.log returns old value of input?
I want to pass formik as a prop to Child Component CustomFormGroup and my Parent component is the Profile I'm trying to follow this answer. In Proflie component I'm getting Error. Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
Component
import Input from "antd/lib/input/Input";
import { customFormGroupProps } from "../types";
import { useFormikContext } from "formik";
const CustomFormGroup = (props: customFormGroupProps) => {
const context = useFormikContext<customFormGroupProps>();
console.log(context);
return (
<div>
<label className='mt-1'>{props.labelName}</label>
<Input
placeholder={props.placeholder}
/>
</div>
);
};
export default CustomFormGroup;
type is as follows
export interface customFormGroupProps {
labelName: string;
placeholder: string;
}
Parent Component
import CustomFormGroup from "../utils/CustomFormGroup";
import formik from "./profileConfig";
import Form from "antd/lib/form/Form";
const Profile = () => {
return (
<Form onFinish={formik.handleSubmit}>
<CustomFormGroup labelName="Name" placeholder="Jhon Doe" />
</Form>
);
};
export default Profile;
profileConfig file
import { useFormik } from "formik";
const formik = useFormik({
initialValues:{
name:""
},
onSubmit: values => {
console.log(values.name);
}
});
export default formik;
You can not use hooks outside a component function, it is simply how they work. But, you can make a composition of hooks.
const formik = useFormik({
initialValues:{
name:""
},
onSubmit: values => {
console.log(values.name);
}
});
Just do it like this
import CustomFormGroup from "../utils/CustomFormGroup";
import Form from "antd/lib/form/Form";
import { useFormik } from "formik";
const Profile = () => {
const formik = useFormik({
initialValues:{
name:""
},
onSubmit: values => {
console.log(values.name);
}
});
return (
<Form onFinish={formik.handleSubmit}>
<CustomFormGroup labelName="Name" placeholder="Jhon Doe" />
</Form>
);
};
export default Profile
You cannot call useFormik() in profileConfig file because it is not inside a React component. useFormik() is a custom React hook from Formik and hooks don't work outside React components.
To fix, move it to Profile component.
const Profile = () => {
const formik = useFormik({
initialValues:{
name:""
},
onSubmit: values => {
console.log(values.name);
}
});
return (
<Form onFinish={formik.handleSubmit}>
<CustomFormGroup labelName="Name" placeholder="Jhon Doe" />
</Form>
);
};
I have been practicing with React Context, but I have a problem with storing a user in my general state, the problem is that I have to click the save button twice to save the state in my context, with the first click I only see that the state is as an empty object and with the second click the state is saved (the name and password), this is the code I have. This would be the context of my application:
import { createContext } from "react";
const UserContext = createContext()
export default UserContext
this would be my types file:
export const LOGIN= 'LOGIN'
this is my UserState file:
import { useReducer } from "react"
import UserReducer from './UserReducer'
import UserContext from "./UserContex"
const UserState = ({ children }) => {
const initialState = {
usernew: {}
}
const [state, dispatch] = useReducer(UserReducer, initialState)
const login = (userLogin) => {
dispatch({
type: 'LOGIN',
payload: userLogin
})
window.localStorage.setItem('user', JSON.stringify(state.usernew))
const data = window.localStorage.getItem('user')
console.log(data)
}
return (
<UserContext.Provider
value={{
usernew: state.usernew,
login,
}}
>
{children}
</UserContext.Provider>
)
}
export default UserState
UserReducer file:
import {LOGIN} from "../types";
export default (state, action) => {
const { payload, type } = action;
switch (type) {
case LOGIN:
return {
...state,
usernew: payload
}
default:
return state
}
}
and this would be my component where I am changing the context:
import { Box, Button } from '#chakra-ui/react'
import { Input } from '#chakra-ui/react'
import { useState } from 'react'
import { useContext } from 'react'
import UserContext from '../context/User/UserContex'
const Login = () => {
const { login, usernew} = useContext(UserContext)
const [fields, setFields] = useState({
name: '',
password: ''
})
const handleChange = (e) => {
const name = e.target.name;
const value = e.target.value;
setFields({
...fields,
[name]: value
})
}
return (
<Box w='80%' p={4}>
<Input
placeholder='Name'
onChange={handleChange}
name="name"
value={fields.name}
/>
<Input
placeholder='Password'
onChange={handleChange}
name="password"
value={fields.password}
/>
<Button bg="teal" color="#fff" onClick={() => login(fields)}>Submit</Button>
{usernew ?
<div>
<h2>Usuario con {usernew.name} - {usernew.password}</h2>
</div>
:
<div>
<h1>Sin usuario</h1>
</div>
}
</Box>
)
}
export default Login
I checked all the code but I can't find the error, that's why I'm showing all the code I have. Thank you for your attention
How to pass text value to another component using Redux in React?
I am learning Redux in React. I am trying to pass text value to another component using Redux in React.
My code is like below
Mycomponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.dispatch({ type: "add" });
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
export default connect(mapStateToProps)(Mycomponent);
nameAction.js
export const nameAction = () => ({
type: 'add'
});
export default { nameAction };
nameReducer.js
const nameReducer = (state = {}, action) => {
switch (action.type) {
case 'add': {
return {
...state,
nameState: action.payload
};
}
default:
return state;
}
};
export default nameReducer;
Outputcomponent.js
import React, { Component } from 'react';
class Outputcomponent extends Component {
render = (props) => {
return (
<div>
<div>{this.props.nameState }</div>
</div>
);
}
}
export default Outputcomponent;
The use of redux hooks explained by Josiah is for me the best approach but you can also use mapDispatchToProps.
Even if the main problem is that you don't pass any data in your 'add' action.
nameAction.js
You call the action.payload in nameReducer.js but it does not appear in your action
export const nameAction = (text) => ({
type: 'add',
payload: text
});
Mycomponent.js
Then as for your state we can mapDispatchToProps.
(I think it's better to trigger the action with a submit button and save the input change in your textInput state, but I guess it's intentional that there is none)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {nameAction} from './nameAction'
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.nameAction(event.target.value);
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
const mapDispatchToProps = dispatch => ({ nameAction: (text) => dispatch(nameAction(text))});
export default connect(mapStateToProps,mapDispatchToProps)(Mycomponent);
OutputComponent.js
to get the data two possibilities either with a class using connect and mapStateToProps , or using the useSelector hook with a functional component.
with a Class
import React, { Component } from "react";
import { connect } from "react-redux";
class OutputComponent extends Component {
render = () => {
return (
<div>
<div>{this.props.nameState}</div>
</div>
);
};
}
const mapStateToProps = state => state;
export default connect(mapStateToProps)(OutputComponent);
with a functional component
import React from "react";
import { useSelector } from "react-redux";
const OutputComponent = () => {
const nameState = useSelector((state) => state.nameState);
return (
<div>
<div>{nameState}</div>
</div>
);
};
export default OutputComponent;
Of course you must not forget to create a strore and to provide it to the highest component
store.js
import { createStore } from "redux";
import nameReducer from "./nameReducer";
const store = createStore(nameReducer);
export default store;
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Component
const AddTodo = () => {
const [todo, setTodo] = useState("");
const dispatch = useDispatch();
const handleChange = (e) => setTodo(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addTodoAction(todo));
}
return {
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
</form>
}
)
Actions
const addTodoAction = (text) => {
dispatch({
type: "ADD_TODO",
payload: text
})
}
Reducers
const addTodoReducer = (state, action) => {
switch(action.type) {
case "ADD_TODO":
return {
todo: action.payload,
}
default:
return state;
}
}
store
// some code for store.js
Accessing this todo from another component
const ComponentA = () => {
const {todo} = useSelector(state => state.todo);
return (
<p> {todo} </p>
)
}
Side Note:
Redux comes with too much boilerplate if you want to pass text from one component to another, just use props
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 }]
})
}