How can I do to do mutations using React.js? - reactjs

I am trying to do some mutations using graphql and react.js but I got a problem. Indeed I got the following message :
ESLint: React Hook "useMutation" is called in function "onSubmit" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter.(react-hooks/rules-of-hooks)
But I need to do the mutation when I click to validate the form and for that I need the function "onSUbmit"
Here is my code :
import React from "react";
import { Modal, Button } from "react-bootstrap";
import {useForm} from "react-hook-form";
import {gql, useMutation, useQuery} from '#apollo/client';
import {BrowserRouter, Link, Redirect} from "react-router-dom";
const Register = (props) => {
const { register, handleSubmit, errors } = useForm();
const onSubmit = data => {
let username = data.Username;
const GET_ACTIVITY = gql`
mutation Register($username: String!){
register(username: $username){
username
}
}
`
const [addchannel, { datas} ] = useMutation(GET_ACTIVITY);
}
console.log(props);
return (
<Modal show={props.show} onHide={props.onClose} centered>
<div className="login-form">
<h3 className="h3 mb-3 font-weight-normal" style={{textAlign: "center"}}> Register</h3>
<form className="form-signin" onSubmit={handleSubmit(onSubmit)} >
<div className="form-group">
<input
type="text"
id="inputUsername"
className="form-control"
placeholder="Username"
required=""
autoFocus=""
name="Username"
ref={register({ required: true})}
/>
<button className="btn btn-outline-success btn-block" type="submit" >
<i className="fas fa-sign-in-alt" /> Register
</button>
<hr />
</div>
</form>
</div>
</Modal>
);
}
export default Register;
Could you help me please ?
Thank you very much !

This line const [addchannel, { datas} ] = useMutation(GET_ACTIVITY); doesn't actually call the mutation. It just gives you the means to do it. You then have to call addChannel elsewhere in your code. That's why the call to useMutation has to be outside the onSubmit function. In your onSubmit function, you then call addChannel(). The component will then rerender and you can use datas.
EDIT : It seems to me that you though you could pass the username variable directly to the template literal. YOU WILL NEVER HAVE TO DO THIS! And even so, you'll have to pass it like this :
gql`
mutation Register($username: String!){
register(username: ${$username}){
username
}
}
`
But again, you should never have to build dynamic queries like this.
You have to call your mutation like so :
addChannel({ // this is the function returned by useMutation
variables: {
username // that's where you pass username !
}
})
So, there's no need to have GET_ACTIVITY inside your function, let alone the call to useMutation.

Don’t call Hooks inside loops, conditions, or nested functions.
you can try this.
const Register = (props) => {
const { register, handleSubmit, errors } = useForm();
const [addChannel, { datas} ] = useMutation(GET_ACTIVITY);
const GET_ACTIVITY= gql`
mutation Register($username: String!){
register(username: $username){
username
}
}
`
const onSubmit = data => {
addChannel({ variables: { username: data.Username } });;
}
...

Related

How can I push my react form data to a dynamic API query in another component

this is my first StackOverflow question:
So I have a form called datepicker, and the idea is that a user can put in a date in YYYY-MM-DD format.
Upon the submission of the entry in the field (onSubmit handler), the user is to be redirected to a /guage/:date route, that displays a guage chart component (rendered via google charts).
This guage chart is based on data pre-fetched from an API endpoint, but the {date} parameter for the API endpoint needs to be dynamic.
Meaning, when a user inputs 2022-06-09 to the date field in calendarPicker.js, there should be a redirect on submit, to the web page /guage/2022-06-09.
The problem I am having, is that the form renders, I enter a YYY-MM-DD date, the state is saved, but the handleSubmit is never triggered, and no re-direct to 'guage/:date' takes place.
Here is CalendarPicker.JS:
import React from 'react';
import { Link, Navigate } from 'react-router-dom';
export default class CalendarPicker extends React.Component {
constructor(props) {
super(props);
this.state = {
input:''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handleSubmit(event) {
return(
<Navigate
to={{
pathname: '/guage/:input',
state: {input: this.state.input}
}}
/>)
}
render() {
return (
<div>
<form>
<label>date
<input type="text" name="input" value={this.state.input} onChange={this.handleChange} placeholder="input" required="true" />
<button variant="primary" type="submit" onClick={this.handleSubmit}>
Submit
</button>
</label>
</form>
</div>
);
}
}
Here is DelayGuage.JS (this is where i draw the guage chart):
//guage/2022-06-09
//const data = await Promise.resolve(resolution);
//parse URL using use params from react router
//in order to have application that scales, you need to reduce coupling between components, de-bounding components to URLs
//in order to have less coupled component and be able to render multiple components at once, using an abstraction layer,
//known as react router, using the useparams hook, you extract the parameters without knowledge of the URL structure
import React from "react";
import { Chart } from "react-google-charts";
import axios from "axios";
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { Link, useLocation, useParams } from "react-router-dom";
async function fetchPosts() {
const {data} = await axios.get(
"http://102.12.11.102:5000/reports/{date}/agencies"
);
const parsedData = data.agencies[0].kpis.map((r) => [
"Delay Index",
r.kpi_value * 100
]);
return [["Label", "Value"], ...parsedData];
}
export const options = {
width: 480,
height: 480,
redFrom: 90,
redTo: 100,
yellowFrom: 75,
yellowTo: 90,
minorTicks: 5
};
export function DelayGuage() {
const { data, status } = useQuery("data", fetchPosts);
return (
status === "success" && (
<Chart
chartType="Gauge"
width="100%"
height="400px"
data={data}
options={options}
/>
)
);
}
export default DelayGuage
Navigate is a React component, it needs to be rendered to have any effect. You should use the useNavigate hook if you want to issue an imperative navigation action from a callback. For this though you'll need to either convert CalendarPicker to a React function component or create a custom withRouter Higher Order Component.
Conversion:
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
const CalendarPicker = () => {
const navigate = useNavigate();
const [input, setInput] = React.useState('');
const handleChange = (event) => {
setInput(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
navigate(`/gauge/${input}`);
};
return (
<div>
<form onSubmit={handleSubmit}>
<label>
date
<input
type="text"
name="input"
value={input}
onChange={handleChange}
placeholder="input"
required
/>
</label>
<button variant="primary" type="submit">
Submit
</button>
</form>
</div>
);
};
export default CalendarPicker;
withRouter HOC:
import React from 'react';
import { useNavigate, /* other hooks */ } from 'react-router-dom';
const withRouter = Component => props => {
const navigate = useNavigate();
// other hooks
return (
<Component
{...props}
{...{ navigate, /* other hooks props */ }}
/>
);
};
export default withRouter;
...
import React from 'react';
import { Link } from 'react-router-dom';
import withRouter from '../path/to/withRouter';
class CalendarPicker extends React.Component {
state = {
input: '',
}
handleChange = (event) => {
this.setState({ input: event.target.value });
}
handleSubmit = (event) => {
event.preventDefault();
this.props.navigate(`/gauge/${input}`);
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
date
<input
type="text"
name="input"
value={this.state.input}
onChange={this.handleChange}
placeholder="input"
required
/>
</label>
<button variant="primary" type="submit">
Submit
</button>
</form>
</div>
);
}
}
export default withRouter(CalendarPicker);

How do I get it to exit loading using useLazyQuery?

First of all, hello everyone. I'm trying to improve myself with Graphql. Normally, I can display the GET_CHARACTERS variable that I define by calling useQuery. There is no problem here. But I want to display the data that will correspond to the value I entered in the input field. I used useLazyQuery for this. But here is the problem. I enter the text and then click the button. It stays on Loading all the time. It doesn't switch to display. I've also looked at other articles. I've also applied all the fetchPoliciys in useLazyQuery, but the result hasn't changed at all. I also tried notifyOnNetworkStatusChange: true, and it didn't work either.
I would appreciate your help. Thanks in advance.
import React, { useEffect, useState } from "react";
import { useQuery, gql, useLazyQuery } from "#apollo/client";
import Card from "./components/Card";
const GET_CHARACTERS = gql`
query GetCharacterLocations($name: String!) {
characters(filter: { name: $name }) {
results {
id
name
image
location {
name
}
}
}
}
`;
const App=()=> {
const [name, setName] = useState("");
const [getRickMorty, { loading, error, data, called }] = useLazyQuery(
GET_CHARACTERS,
{
variables: { name },
}
);
console.log({ data, loading, error });
return (
<div className="container">
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button
onClick={() => {
getRickMorty();
}}
>Search
</button>
{loading && <div>Loading</div>}
{error && <div>Error</div>}
{data?.characters?.results?.map((characters) => (
<Card character={characters} />
))}
</div>
);
}
export default App;

Passing a function works, but doesn't change the variable

I'm passing a function to another component in the link. In another component, I make a query to api, when the query is executed I go back to the previous page and execute the "openFromLogin" function. The function executes because it returns console.log, but the variable is still false.
I want to do so that after logging in and redirecting it automatically opens the modal.
Please help, thanks :)
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import Modal from 'react-modal';
function ProductSidebarOne(props) {
const [openPricing, setOpenPricing] = useState(false);
const openFromLogin = () => {
setOpenPricing(true);
console.log("done");
}
console.log(openPricing);
return (
<>
<Link to={{ pathname: `/login`, state: { from: props.location.pathname }, openFromLogin }} className="font-orange font-semibold">Log in</Link>
<Modal
isOpen={openPricing}
shouldFocusAfterRender={false}
className="pricing-popup"
closeTimeoutMS={10}
>
<div className="modal-dialog modal-dialog-centered" role="document">
<div className="modal-content">
<div className="modal-body">
<button type="button" className="close" aria-label="Close" style={{ position: 'absolute', right: '0', top: '0' }}>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-link btn-sm">Close</button>
<button type="submit" className="btn btn-primary btn-sm">Send</button>
</div>
</div>
</div>
</Modal>
</>
)
}
export default ProductSidebarOne;
import React, { useState } from 'react';
import axios from 'axios';
import { setUserSession } from '../../../../utils';
function Login(props) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const openFromLogin = props.location.openFromLogin;
const handleLogin = (e) => {
e.preventDefault();
axios.post("api url", {
email: email,
password: password
})
.then(response => {
setUserSession(response.data);
props.history.push(props.location.state.from, openFromLogin());
});
}
return (
<div className="login-page">
<form onSubmit={handleLogin} className="mb-0">
<input type="text" className="form-control" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" className="form-control" value={password} onChange={e => setPassword(e.target.value)} />
<div className="form-footer">
<button type="submit" className="btn btn-primary">Log in</button>
</div>
</form>
</div>
)
}
export default Login;
Duplicated in Pass props in Link react-router
AFAIK you cannot do that with Link. You would have to pass either path or query parameters, since Link is basically just an anchor to an url.
What you could do instead is setting up a "global" state with context which gets manipulated after successfully logging in. Your ProductSidebarOne could then subscribe, "use", that context and react to it, if it fulfills the requirements.
Example: Setup a AuthenticationContext and set loggedIn to true after successfully logging in. useContext for AuthenticationContext in ProductSidebarone and useEffect on loggedIn from the context to setOpenPricing(true).
I hope this clarifies it.
EDIT:
I will try to provide a minimal example, including the missing provider. Note that this is not tested.
import React, { createContext } from 'react';
// Create and export context
export const AuthenticationContext = createContext({
loggedIn: false,
setLoggedIn: () => {},
});
//e.g. App.js
import { AuthenticationContext } from 'PATH';
const App = () => {
...
// Hold state for context
const [loggedIn, setLoggedIn] = useState(false);
return (
...
// Provide context to child components in app
<AuthenticationContext.Provider value={{loggedIn, setLoggedIn}}>
...
</AuthenticationContext.Provider>
)
}
import React, { useState, useContext, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Modal from 'react-modal';
import { AuthenticationContext } from '../../../others/common/context';
function ProductSidebarOne(props) {
const [openPricing, setOpenPricing] = useState(false);
// Connect to context
const { loggedIn } = useContext(AuthenticationContext);
// Here we can use the state inside context
// e.g. react to change in context
useEffect(() => {
if(loggedIn) setOpenPricing(true)
}, [loggedIn])
return (...)
}
import React, { useState, useContext } from 'react';
import axios from 'axios';
import { setUserSession } from '../../../../utils';
import { AuthenticationContext } from '../common/context';
function Login(props) {
...
const { loggedIn, setLoggedIn } = useContext(AuthenticationContext);
const handleLogin = (e) => {
e.preventDefault();
axios.post("api url", {
email: email,
password: password
})
.then(response => {
setUserSession(response.data);
// Manipulate state from any child
setLoggedIn(true);
props.history.push(props.location.state.from);
});
}
return (...)
}
export default Login;

Getting Invalid hook call error on importing a component in React

I have built a login page where on filling the username and password and hitting the submit button, it dispatches the action which basically goes to the backend and checks whether the username and password is valid. I am using React and redux and this is my login page below
login.js (simplified)
import LoginHandle from '../login/LoginHandle'
function userLogin(event) {
event.preventDefault()
const username = document.getElementById("username").value
const password = document.getElementById("password").value
LoginHandle(username, password)
}
const Login = () => {
return (
<div className="flex-row align-items-center">
<form onSubmit={(e) => userLogin(e)}>
<h1>Login</h1>
<input id="username" type="email" placeholder="Username" required/>
<input id="password" type="password" placeholder="Password" required/>
<button type="submit" className="px-4">Login</button>
</form>
</div>
)
}
export default Login
LoginHandle.js (simplified)
import {useDispatch, useSelector} from 'react-redux'
import {getLoginStatus} from '../actions/loginAction'
const LoginHandle = (username, password) => {
const dispatch = useDispatch()
dispatch(getLoginStatus(username, password))
const loginState = useSelector(state=> state.loginStatus)
if(loginState.loading) {
console.log("please wait")
}
// .... rest of the logic
}
export default LoginHandle
As you can see I try to dispatch the action and then check the state to get confirmation from the backend. But I get this error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:...
What am I doing wrong?
import {useDispatch, useSelector} from 'react-redux'
import {getLoginStatus} from '../actions/loginAction'
const useLoginHandler = (username, password, submitted) => {
const dispatch = useDispatch();
const loginState = useSelector(state=> state.loginStatus)
// it is better to put the dispatch inside a useEffect rather than outside
useEffect(() => {
if(!submitted) {return};
dispatch(getLoginStatus(username, password))
}, [username, password, submitted]);
if(loginState.loading) {
console.log("please wait")
}
// .... rest of the logic
}
export default useLoginHandler
And your usage of the hook is wrong. it should not be inside a callback function.
it should be at the top level. Something like below. Also you should not access dom elements directly, instead use useRef or useState to get value
const Login = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('')
const [submitted, setSubmitted] = useState(false);
useLoginHandler(username, password, submitted); //custom hook should be used at this level
return (
<form onSubmit={(e) => setSubmitted(true)}>
<input id="username" type="email" placeholder="Username" required onChange={e => setUsername(e.target.value)}/>
</form>
)
}
You are using 2 hooks in this implementation useDispatch & useSelector and calling them inside a function, which is invalid usage. Hooks can only be called inside a functional component. So changing your above implementation as below should work:
Login.js
import {useDispatch, useSelector} from 'react-redux'
import {getLoginStatus} from '../actions/loginAction'
const Login = () => {
const dispatch = useDispatch()
const loginState = useSelector(state=> state.loginStatus)
function userLogin(event) {
event.preventDefault()
const username = document.getElementById("username").value
const password = document.getElementById("password").value
dispatch(getLoginStatus(username, password))
}
return (
<div className="flex-row align-items-center">
<form onSubmit={(e) => userLogin(e)}>
<h1>Login</h1>
<input id="username" type="email" placeholder="Username" required/>
<input id="password" type="password" placeholder="Password" required/>
<button type="submit" className="px-4">{loginState.loading ? "Please wait..." : "Login"}</button>
</form>
</div>
)
}
export default Login
Things to note:
Called useDispatch directly in functional component and not inside a function of functional component. Then we can use the result of useDispatch inside a function
Called useSelector inside functional component and then used the output to show loading in place of text in the button

Is using useImperativeHandle hook justified here with Draft.JS editor and Formik?

I'm making a small CMS system in React and I have a form where users can use the Draft.js editor along with some other fields. For the question in mind, let's focus on the edit form.
The code for the editor looks like this:
import React, { useRef } from 'react';
import { Formik } from "formik";
import TextInputField from "#/components/TextInputField";
import client from "#/utils/http";
const MyForm = ({title, content}) => {
const editorRef = useRef();
function handleSubmit(values) {
const editorContent = editorRef.current.parse();
client.submit('/api/edit/project', { editorContent, ...values });
}
return (
<Formik onSubmit={formik.handleSubmit} initialValues={{ title }}>
{
(formik) => (
<form onSubmit={formik.handleSubmit}>
<TextInputField label="title" name="title" />
<RichEditor ref={editorRef} content={content} />
</form>
)}
</Formik>);
}
And I have the editor code:
import React, { useImperativeHandle, useState } from "react";
import {
Editor,
EditorState,
convertFromHTML,
ContentState,
convertToRaw,
} from "draft-js";
import draftToHtml from "draftjs-to-html";
function createFromContent(htmlContent) {
const blocksFromHtml = convertFromHTML(htmlContent);
const editorState = ContentState.createFromBlockArray(
blocksFromHtml.contentBlocks,
blocksFromHtml.entityMap
);
return EditorState.createWithContent(editorState);
}
function formatToHTML(editorState) {
const raw = convertToRaw(editorState.getCurrentContent());
const markup = draftToHtml(raw);
return markup;
}
function RichEditor({ content = null }, ref) {
const [editorState, setEditorState] = useState(() =>
content ? createFromContent(content) : EditorState.createEmpty()
);
useImperativeHandle(
ref,
() => ({
parse: () => {
return formatToHTML(editorState);
},
}),
[editorState]
);
return (
<div className="App-Rich-Editor w-full block border border-gray-300 rounded-md mt-4 shadow-sm">
<Editor
placeholder="Enter your content..."
editorState={editorState}
onChange={setEditorState}
/>
</div>
);
}
export default React.forwardRef(RichEditor);
This works, but it brings me to the following questions and hence why asking the community because using useImperativeHandle seems like a "hack". Since even the React documentation discourages the use of it.
As always, imperative code using refs should be avoided in most cases.
Since I wanted to format the editor's internal state only once, when I submit the form, is the code I shown justified even though it "swims against the react tide", by using the imperative handle to share the child state with the parent.
This brings me to the questions:
Is it okay to useImperativeHandle hook in this case, for "optimization" so we grab the state only when we need to?
Is there some better way to achieve this implementation with "usual" patterns, like "lifting state up", "render props" or something else?
Am I overlooking the problem here, and should I just bite the bullet and sync the entire editor state with formik, by lifting it up from the component, and then format it on submit?
To me, the third option seems to break the separation of concerns, as it would pollute the Form context with state logic that just feels like it doesn't belong there.
In my humble opinion, the provided solution is a bit overengineering. So let me just provide my thoughts on the questions you asked:
I don't see the optimization in using useImperativeHandle as the value is stored both in ref and in RichEditor state
the formatToHTML function seems to be pure function. So why not export it and use exactly before form submition instead of complicating things with forwardRef and useImperativeHandle
Here is what I propose and I think it is exactly what you mentioned in the 3rd bullet:
import TextInputField from "#/components/TextInputField";
import client from "#/utils/http";
import {
ContentState,
convertFromHTML,
convertToRaw,
Editor,
EditorState,
} from "draft-js";
import draftToHtml from "draftjs-to-html";
import { Formik } from "formik";
import React, { useCallback } from "react";
function createFromContent(htmlContent) {
const blocksFromHtml = convertFromHTML(htmlContent);
const editorState = ContentState.createFromBlockArray(
blocksFromHtml.contentBlocks,
blocksFromHtml.entityMap
);
return EditorState.createWithContent(editorState);
}
function formatToHTML(editorState) {
const raw = convertToRaw(editorState.getCurrentContent());
const markup = draftToHtml(raw);
return markup;
}
const MyForm = ({ title, content }) => {
const [editorState, setEditorState] = useState(() =>
content ? createFromContent(content) : EditorState.createEmpty()
);
const handleSubmit = useCallback(
(values) => {
const editorContent = formatToHTML(editorState);
client.submit("/api/edit/project", { editorContent, ...values });
},
[editorState]
);
return (
<Formik onSubmit={handleSubmit} initialValues={{ title }}>
{(formik) => (
<form onSubmit={formik.handleSubmit}>
<TextInputField label="title" name="title" />
<div className="App-Rich-Editor w-full block border border-gray-300 rounded-md mt-4 shadow-sm">
<Editor
placeholder="Enter your content..."
editorState={editorState}
onChange={setEditorState}
/>
</div>
</form>
)}
</Formik>
);
};
export default MyForm;

Resources