Using React Javascript (Form-Onsubmit & calling API not working properly) - javascript

i am a bit puzzled with the logic when reading the below code, although the code is working but not exactly as i would like it to behave.
3 queries i have if some one can please clarify.
1- As i understand useEffect is used to invoke the function after render, but in the below code, once the form is sumbitted (onSubmit={credentialVerify}) it will call the credentialVerify() function as below, so i dont think we need useEffect here, but still the code doesnt call the API unless i use the useEffect statement.
2- Also doesnt wait for me to enter my credentails first and as soon as i go to the Signin page it will fetch the API’s (when using useEffect ) and shows the result in the windows, but i try to design in a way that when i click button then it will fetch the API
3- when in the form onsubmit call the credentialVerify function, i have console.log(e) but it is showing as undefined, but as i understand onsubmit will call the function and through the event argument by default.
Below is the snippet of my code.
Any help Appreciated.
import React, { useState, useEffect } from "react";
import "../App.css";
import { Link } from "react-router-dom";
function Signin() {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const updateName = (e) => {
setName(e.target.value);
};
const updatePassword = (e) => {
setPassword(e.target.value);
};
const [items, setItems] = useState([]);
useEffect(() => { //Point-1 useEffect- API not call atall without this statement
credentialVerify();
}, []);
const credentialVerify = async (e) => {
console.log(e); //Point-3 this is coming as undefined
const data1 = await fetch("http://localhost:5000/api/customers");
const incomingdata = await data1.json();
console.log(data1);
console.log(incomingdata);
console.log(name, password);
setItems(incomingdata);
};
return (
<div>
<div>
{
<form className="formstyle" onSubmit={credentialVerify}>
<input
type="text"
placeholder="Username"
name="username"
value={name}
onChange={updateName}
/>
<input
type="text"
placeholder="Password"
name="password"
value={password}
onChange={updatePassword}
/>
<button type="submit">Submit</button>
</form>
}
</div>
<div>
{items.map((entry) => {
let key = entry.email;
let valuefirst = entry.firstName;
let valuelast = entry.created_at;
return (
<p key={key}>
{key}: {valuefirst}bb {valuelast}
</p>
);
})}
</div>
</div>
);
}
export default Signin;

For your first question, you are correct - it doesn't make sense to call credentialVerify when your component renders for the first time since that seems to be the handler for when your form gets submitted. Unless you're fetching data prior to displaying your form, you can drop the useEffect hook entirely.
This is also takes care of your second question because the hook will run once when your component renders for the first time, which is indicated by the empty array [] used as a dependency array of the useEffect hook. This is equivalent to componentDidMount in a class-based component, but again, it doesn't make sense to call credentialVerify at this point.
As for your third question, you should probably do something like the following:
const credentialVerify = event => {
event.preventDefault();
(async () => {
const data = await fetch("http://localhost:5000/api/customers")
.then(res => res.json());
.catch(e => e);
console.log(incomingData);
// ...
})();
}
Since you're passing an asynchronous function as your event handler, you might have issues accessing the SyntheticEvent object due to the reasons stated in React docs:
The SyntheticEvent is pooled. This means that the SyntheticEvent object will be reused and all properties will be nullified after the event callback has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way.
reactjs.org/docs/events.html#event-pooling
Your final component should look like the following:
function Signin() {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const [items, setItems] = useState([]);
const updateName = e => {
setName(e.target.value);
};
const updatePassword = e => {
setPassword(e.target.value);
};
const credentialVerify = event => {
event.preventDefault();
(async () => {
const incomingdata = await fetch("http://localhost:5000/api/customers")
.then(res => res.json())
.catch(e => e);
console.log(incomingdata);
console.log(name, password);
setItems(incomingdata);
})();
};
return (
<div>...</div>
);
}

Related

Updating State in React Component causing it to get unmounted

I have a component where-in I need to fetch some data and render it. The component gets rendered initially. The problem I'm facing is when the handler function switchDocumentType is called after clicking the button for a particular type, the whole component gets unmounted/un-rendered.
While debugging on my own I found this happens after setDocumentType is run inside event handler function.
What is wrong in the below code snippet that could possibly cause this issue? I can see the useEffect is not going in infinite-loop as well.
Code snippet:
import * as React from 'react';
const MyComponent = (props) => {
const [documentType, setDocumentType] = React.useState('alpha');
const [documentData, setDocumentData] = React.useState('');
const types = ['alpha', 'beta', 'gamma'];
React.useEffect(() => {
myDataFetch('https://example.com/foo/?bar=123').then(async (response) => {
const data = await response.json();
setDocumentData(data.terms); // html string
const myDiv = document.getElementById('spacial-div');
myDiv.innerHTML = data; // need to render raw HTML inside a div
});
}, [documentType]);
const switchDocumentType = (type) => {
setDocumentType(type);
// send some analytics events
};
const convertToPDF = () => {
// uses documentData to generate PDF
};
return (
<div className="container-div">
{types.map((type) => {
return (
<button key={type} onClick={(type) => switchDocumentType(type)}>
{type}
</button>
);
})}
<div id="special-div" />
</div>
);
};
export default MyComponent;
You shouldn't edit the DOM directly. React has two DOMs, a virtual DOM and a real DOM. Rendering can be a bit finicky if you decide to edit the real DOM.
You can parse html safely, by using html-react-parser. This is the best way to do it, because it becomes part of the react tree whereas dangerouslySetInnerHTML will replace the entire HTML to flush changes to the DOM. With reconciliation, it can create exponential load times.
It will also sanitize your inputs, you know.. for safety. :)
import parse from 'html-react-parser';
const SpecialDiv = ({html}) => {
const reactElement = parse(html);
return reactElement
}
If you decide that you must use dangerouslySetInnerHTML you can do it as so:
const [someHTML, setSomeHTML] = useState(null)
const someFunction = async() => {
const response = await getData();
const data = await response.json();
setSomeHTML(data);
}
return(
<div>
{someHTML && <div dangerouslySetInnerHTML={{__html: someHTML}} id="special-div"/>}
</div>
)
That being said, I would say that by allowing this, you open yourself up to the possibility of a XSS attack, without properly parsing and purifying your inputs.
Do not use useEffect as handler, use useEffect hooks for initializations.
Instead of using/setting innerHtml, let react do it for you.
I suppose you have myDataFetch defined somewhere and I don't see your data fetch using the type.
Anyways, try to use the modified code below.
import * as React from 'react';
const MyComponent = (props) => {
const [documentType, setDocumentType] = React.useState('alpha');
const [documentData, setDocumentData] = React.useState('');
const types = ['alpha', 'beta', 'gamma'];
const fetchData = async () => {
const response = await myDataFetch('https://example.com/foo/?bar=123')
const data = await response.json();
setDocumentData(data);
}
React.useEffect(() => {
fetchData();
}, []);
const switchDocumentType = async (e, type) => {
e.preventDefault();
setDocumentType(type);
await fetchData();
// send some analytics events
};
return (
<div className="container-div">
{types.map((type) => {
return (
<button key={type} onClick={(e) => switchDocumentType(e, type)}>
{type}
</button>
);
})}
<div id="special-div">{documentData}</div>
</div>
);
};
export default MyComponent;
Not sure why but placing debuggers before state update causes this issue, not only for this component, but for all the other components I tried with. Seems to be an issue either with debugger or React. Removing debuggers solved the issue.
Also, now I'm returning a cleanup function inside useEffect as pointed out in some stack-overflow posts. I also refactored the code as suggested by #iaq and #sheepiiHD to follow React best practices.
Updated code:
import * as React from 'react';
const MyComponent = (props) => {
const [documentType, setDocumentType] = React.useState('alpha');
const [documentData, setDocumentData] = React.useState('');
const types = ['alpha', 'beta', 'gamma'];
const fetchData = async () => {
const response = await myDataFetch('https://example.com/foo/?bar=123')
const data = await response.json();
setDocumentData(data);
}
React.useEffect(() => {
fetchData();
return () => {
setDocumentType('');
setDocumentData('');
};
}, []);
const switchDocumentType = async (e, type) => {
e.preventDefault();
setDocumentType(type);
await fetchData();
// send some analytics events
};
return (
<div className="container-div">
{types.map((type) => {
return (
<button key={type} onClick={(e) => switchDocumentType(e, type)}>
{type}
</button>
);
})}
<div id="special-div" dangerouslySetInnerHTML={{__html: documentData.terms}} />
</div>
);
};
export default MyComponent;

Custom react hook with useeffect, cant use in non-component function

I made a custom react hook, which has a useEffect and, for now, returns a set of different states. It's a hook for axios, and the gist of it is this:
export default function useAxios({ url, data = {}, method = "GET"} ) {
var [loading, setLoading] = useState(true)
var [data, setData] = useState(null)
useEffect(function() {
(async function() {
// do axios request and set loading and data states
})()
return () => // cleanup function that cancels axios request
}, [])
return {
loading,
data
}
}
Now, in a simple component I can easily use this custom hook - but my question is: What if I want to use my hook inside an event handler, say:
export default MyComponent() {
function handleSubmit(e) {
var { data } = useAxios({
url: "/my-end-point",
data: {
testInput: e.target.testInput.value
}
})
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="testInput" />
<button type="submit">Submit</button>
</form>
)
}
Problem is my useAxios hook has a useEffect, and so I cannot use it inside a non-component function, i.e. handleSubmit. So what is the work around? Is there even one? Thanks in advance.
As to React's
Only Call Hooks from React Functions, you should always:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks.
Fail to satisfy these two rules leads to unexpected render result out of React.
With those rules in mind, you should return a submitHanlder from react hook instead of just passing the hook function into another component as a callback function.
I might guess that your intention is to trigger the axios request on the submit event. If so, it is possible to achieve that without passing whole hook into event handler.
First of all, as the rules say, you have to make sure your hook got called in every render. So the MyComponent can be rewrite in the below way:
export default function MyComponent() {
var startRequest = useAxios({url: "/my-end-point"}) //<---- useAxios now returns the startRequest function, and will always be called on every render
return (
<form onSubmit={(e) => {
e.preventDefault()
startRequest({testInput: e.target.testInput.value}) // <----- call your startRequest here in the submit hanlder
.then(data => {
//process your data here
})
}}>
<input type="text" name="testInput" />
<button type="submit">Submit</button>
</form>
)
}
Please note that now the hook returns a function startRequest which you can put in your handler, and trigger that handler any time appropriated.
And rearrange your hook's code like below:
export function useAxios({ url, method = "GET"} ) {
var [loading, setLoading] = useState(true)
// <------ no setData here
var startRequest = async function(body = {}) { // <------ Move your input here
// do axios request and set loading and data states
setLoading(true)
await data = axios.post(body)
setLoading(false)
return data // <------- return data as promise
}
var cancelRequest = () => // cleanup function that cancels axios request
useEffect(function() {
return cancelRequest
}, []) // useEffect only helps your cancel request on unmounted.
return startRequest
}
The useEffect now only helps you cleanup axios request without the need to start one, since firing a request should be an event handler's job.
And since the data return by axios is in a promise, you don't need to explicitly setData to store your response data so I removed the line of useState(null).
I would take a look at popular libraries like SWR (useSWR) and apollo-client (useQuery). They're approach is something like this when making get requests
const MyComponent = () => {
const [shouldSkip, setShouldSkip] = useState(true);
const queryResult = useQuery('my-url', {skip: shouldSkip});
const handleSubmit = () => {
setShouldSkip(false);
// this will cause the component to rerender, and skip will now be false
}
}
When making post requests, its something like this:
const MyComponent = () => {
//useMutation returns a callable function whenever you want
const callFunction = useMutation('my-url');
const handleSubmit = () => {
await callFunction()
}
}
You can also take a look at axios-specific hooks like https://github.com/simoneb/axios-hooks, another common pattern they use is to include a refetch function as a result of the hook, that can be called at anytime (like in an event handler)
The point of the hook is not to make the request for you, the point of the hook is to communicate the internal state of stuff (the axios request, in your case) to the component, so that you can render stuff based around that state (like loading states, or the data).
In your case, you can change the value of the query based on the component state, and have the hook return the data to the component based on its parameters. Something like this:
const useAxios = ({ query }) => {
var [loading, setLoading] = useState(true)
var [data, setData] = useState(null)
useEffect(function () {
(async function () {
setLoading(true)
// do axios request and set loading and data states
const request = await axios.get('endpoint', { query })
setData(request.data)
setLoading(false)
})()
return () => { }// cleanup function that cancels axios request
}, [])
return {
loading,
data
}
}
const Component = () => {
const [query, setQuery] = useState('')
const { loading, data } = useAxios({ query });
const submitHandler = (event) => { setQuery(event.target.testInput.value) }
return (
<>
<form onSubmit={submitHandler}>
<input name="testInput" />
<input type="submit" />
</form>
{loading && (
<>a spinner</>
)}
{data && (
<DataRenderer data={data} />
)}
</>
)
}

Click onClick twice in <Link> to update state

I'm trying to update my state by triggering the onClick event in my <Link>, but only after I click it twice then the output appears in my console, I have tried reading other question similar to this in stackoverflow but still could not find the solution, can anybody tell me what I did worng? Your help would be really great
import React,{useState} from 'react';
import { BrowserRouter as Router, Link } from 'react-router-dom';
const [search, setSearch] = useState("");
const [keyword, setKeyword] = useState("");
const handleChange = (e) =>{
setSearch(e.target.value);
console.log(search);
};
const handleClick = () => {
setKeyword(search);
setSearch('');
console.log(keyword);
};
return(
<div>
<input onChange={handleChange} value={search} placeholder="Search songs, artist, albums"/>
<Link onClick = {handleClick} to={`/songlist/${keyword}`}>Search</Link>
</div>
)
The thing you need to understand is : When you click, you add an event, and it's event is linked on your component and your props.
So you need to preventDefault to tell your event/props/component to act normally. Keep your actually code, but add e.preventDefault() at the begin of each event :)
Example :
const handleChange = (e) =>{
e.preventDefault();
setSearch(e.target.value);
console.log(search);
};
JS is async, so you need to handle effects using useEffect hook.
e.g. :
const [search, setSearch] = useState(null);
const [keyword, setKeyword] = useState(null);
const handleClick = () => {
setKeyword(search);
//setSearch('');
//console.log(keyword);
};
React.useEffect(()=>{
if (keyword) {
console.log(keyword);
setSearch(null);
}
},[keyword]);
can you try this, change
<Link onClick = {handleClick} to={`/songlist/${keyword}`}>Search</Link>
to
<Link to={`/songlist/${search}`}>Search</Link>

list.map is not a function help create-react-app

Hi could someone tell me why I am getting the list.map is not a function error? I am pretty sure my React code makes list an array but I am a beginner so I may have overlooked something
import React, { useState, useEffect } from "react";
import Task from "./Task";
function Home() {
const [text, setText] = useState("");
const [task, setTask] = useState("");
const [list, setList] = useState([]);
useEffect(() => {
setList(list.push(task));
}, [task]);
const addTask = (e) => {
e.preventDefault();
setTask(text);
setText("");
};
const updateText = (e) => {
setText(e.target.value);
};
return (
<div className="Home">
<h3>Home Page</h3>
<form onSubmit={addTask}>
<input type="text" value={text} onChange={updateText} />
<button type="submit">Add</button>
</form>
<div className="listoftasks">
{list.map((t) => (
<Task
text={t}
/>
))}
</div>
</div>
);
}
export default Home;
Array.push() does not return the updated array. Therefore you should use Array.concat() -
useEffect(() => {
setList(list.concat(task));
}, [task]);
Or use spread operator, for immutability functional approach:
useEffect(() => {
setList([...list, task]);
}, [task]);
When task is updating you're setting the new value of list to the result of push which is the new length of the array.
You should push and then update the state
useEffect(() => {
list.push(task);
setList(list.slice());
}, [task]);
Array.push() returns a number, the new length of the now modified in place array.
useEffect(() => setList(list.push(newThing))) // list is now a number!!!
You've turned list into a number, so now for a 3 item array(that once was 2 items) you'll be calling 3.map() and a 3 doesn't have a map method on it.
Worse yet if you just push and reset the reference you'll not update
useEffect(() => {
list.push(newThing)
setList(list) // won't update! list === list is true, and thus does not trigger render
})
You can go about updating the list a few ways by making new array and passing it with concatenation
useEffect(() => setList(list.concat(newThing)) // works
or spreading
useEffect(() => setList([...list, newThing])) // works
Once this is done, it will fail the check to see if the passed entity is the same value. This should allow the render to be triggered and your new updates to appear.

React form submission logic with lifted state and controlled dependence

I've dug myself into a deep rabbit hole with this component in an attempt to use React hooks.
The Parent component handles a dictionary state which is eventually distributed to multiple components.
My problem child component WordInput has a form with a single input. When submitting the form the component is fetching the word's definition from an API and passing on both the word and the definition to the parent which then sets the state in the form of dictionary. So far, so good IF it's the first word in dictionary. The part I'm having trouble with is to submit any subsequent words/definitions.
When the user submits a subsequent word, I want the component to check whether the word already exists in the dictionary that is passed to the child. If it doesn't exist, add it to the dictionary via the submit function.
I think the problem is that I'm trying to do too much with useEffect
I useEffect to:
- set loading
- check and process the dictionary for existing words
- check that definition and word aren't empty and submit both to parent/dictionary
- fetch a definition from an API
In the unprocessed code, I have multiple console.groups to help me keep track of what is happening. The more I add to the component, the more Subgroups and subgroups of subgroups accumulate. Clearly, the approach I'm taking isn't very dry and causes too many re-renders of the component/useEffect functions. For conciseness, I have taken out the console.log entries.
The imported fetchWordDefinition merely processes the fetched data and arranges it correctly into an array.
I don't know how to keep this dry and effective, and any help is appreciated with this rather simple task. My hunch is to keep all the logic to submit the word/definition in the submit handler, and only use useEffect to validate the data prior to that.
import React, { useState, useEffect } from "react";
import fetchWordDefinition from "./lib/utils";
const WordInput = ({ onSubmit, dictionary }) => {
const [definition, setDefinition] = useState([]);
const [cause, setCause] = useState({ function: "" });
const [error, setError] = useState({});
const [loading, setLoading] = useState(false);
const [word, setWord] = useState("");
const [wordExistsInDB, setWordExistsInDB] = useState(false);
useEffect(() => {
const dictionaryEmpty = dictionary.length === 0 ? true : false;
if (dictionaryEmpty) {
return;
} else {
for (let i = 0; i < dictionary.length; i += 1) {
if (dictionary[i].word === word) {
setWordExistsInDB(true);
setError({ bool: true, msg: "Word already exists in DB" });
break;
} else {
setWordExistsInDB(false);
setError({ bool: false, msg: "" });
}
}
}
}, [dictionary, word]);
useEffect(() => {
const definitionNotEmpty = definition.length !== 0 ? true : false;
const wordNotEmpty = word !== "" ? true : false;
if (wordNotEmpty && definitionNotEmpty && !wordExistsInDB) {
onSubmit(word, definition);
setWord("");
setDefinition([]);
}
}, [definition, word, onSubmit, wordExistsInDB]);
useEffect(() => {
if (cause.function === "fetch") {
async function fetchFunction() {
const fetch = await fetchWordDefinition(word);
return fetch;
}
fetchFunction().then(definitionArray => {
setDefinition(definitionArray);
setCause({ function: "" });
});
}
}, [cause, word]);
const handleSubmit = async e => {
e.preventDefault();
setLoading(true);
setCause({ function: "fetch" });
};
return (
<form onSubmit={handleSubmit}>
{error.bool ? <span>{error.msg}</span> : null}
<input
name='word'
placeholder='Enter Word'
type='text'
value={word}
onChange={({ target: { value } }) => setWord(value)}
/>
<input type='submit' />
</form>
);
};
export default WordInput;
There are indeed more useEffect's happening than necessary, as well as most of the state. All you need is the handleSubmit to do the fetching.
const WordInput = ({ onSubmit, dictionary }) => {
const [word, setWord] = React.useState("");
const handleChange = React.useCallback(e => {
setWord(e.currentTarget.value)
}, [])
const handleSubmit = React.useCallback(() => {
//check if word is in dictionary
const wordIsAlreadyThere = dictionary.map(entry => entry.word).includes(word)
//fetch the definition, wait for it, and call submit
if(!wordIsAlreadyThere && word.length > 0){
fetchWordDefinition(word)
.then(definition => {
onSubmit(word, definition)
setWord('')
}).catch(err => console.log(err))
}
}, [])
return (
<form onSubmit={handleSubmit}>
<input
value={word}
onChange={handleChange}/>
<input type='submit' />
</form>
);
}
I think you're missing out on some clarity and what useEffect is for
A functional component gets re-ran everytime either a prop or a state changes. useEffect runs when the component gets created, and we use it for things like doing a first-time fetch, or subscribing to an event handler. The second argument (array of variables) is used so that, if we have for example a blog post with with comments etc, we don't re-fetch everything unless the ID changes (meaning it's a new blog post)
Looking at your code, we have this flow:
User inputs something and hits Submit
Check if the word exists in a dictionary
a. If it exists, display an error message
b. If it doesn't exist, fetch from an API and call onSubmit
So really the only state we have here is the word. You can just compute an error based on if the word is in the dictionary, and the API call is done in a callback (useCallback). You have a lot of extra state that doesn't really matter in a state-way
A simplified version would look like this
const WordInput = ({ onSubmit, dictionary }) => {
const [word, setWord] = useState("")
const [loading, setLoading] = useState(false)
// `find` will find the first entry in array that matches
const wordExists = !!dictionary.find(entry => entry.word === word)
// Ternary operator,
const error = (wordExists) ? "Word already exists in DB" : null
// When user hits submit
const handleSubmit = useCallback(() => {
if (wordExists || !word.length) return;
setLoading(true)
fetchFunction()
.then(definitionArray => {
onSubmit(word, definitionArray)
})
}, [])
return (
<form onSubmit={handleSubmit}>
{error && <span>{error}</span>}
<input
name='word'
placeholder='Enter Word'
type='text'
value={word}
onChange={({ target: { value } }) => setWord(value)}
/>
<input type='submit' onclick={handleSubmit} disabled={wordExists}/>
</form>
);
};
Your component only needs to keep track of the word and the loading flag.
When the user changes the word input it updates the word state.
When the user submits the form the loading state changes. This triggers a useEffect that will first check if the word already exists. If not it proceeds to fetch it and add both the word and its definition to the dictionary.
const WordInput = ({ onSubmit, dictionary }) => {
const [loading, setLoading] = useState(false);
const [word, setWord] = useState("");
useEffect(() => {
if (!loading) return;
const existing_word = dictionary.find(item => item.word === word);
if (existing_word) return;
const fetchFunction = async () => {
const definition = await fetchWordDefinition(word);
// Update the dictionary
onSubmit(word, definition);
// Reset the component state
setWord("");
setLoading(false);
};
fetchFunction();
}, [loading]);
return (
<form
onSubmit={e => {
e.preventDefault();
if (word.length) {
setLoading(true);
}
}}
>
<input
name="word"
placeholder="Enter Word"
type="text"
value={word}
onChange={({ target: { value } }) => setWord(value)}
/>
<input type="submit" />
</form>
);
};
Please let me know if something is not clear or I missed something.

Categories

Resources