React useState hook not fucntioning properly - reactjs

EDIT:
By console.loging outside the async function, I can view the data. I am also trying to pass this data through a Redirect. I skipped a lot of part initially, so here is the full function:
const SearchForm = () => {
const [keyword, setKeyword] = useState('')
const [fetchedData, setFetchedData] = useState([])
const [redirect, setRedirect] = useState(false)
async function fetchData() {
const { data } = await axios.post('http://127.0.0.1:8000/api/posts/search/', {keyword});
setFetchedData(data);
}
const handleSubmit = e => {
e.preventDefault();
fetchData();
setRedirect(true);
}
if (redirect) {
return <Redirect to={{ pathname: '/search', fetchedData }} />
}
return (
<div>
<form onSubmit={ handleSubmit }>
<div className='input-field'>
<input placeholder="Search whatever you wish"
type="text"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
</div>
</form>
</div>
)
}
The fetchedData is not being passed through the Redirect. When I check, its just an empty array (and that is what the initial value was).

This setFetchedData() is an asynchronous process. Try using with a callback to get the right result:
setFetchedData(data, () => {
console.log(fetchedData)
});
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script></script>
<div id="root"></div>
<script type="text/babel">
const { useState } = React
const App = () => {
const [keyword, setKeyword] = useState('Change in a sec...');
const [fetchedData, setFetchedData] = useState([]);
setTimeout(() => {
setKeyword("Hello, changed!", () => {
console.log(keyword);
});
}, 1000);
return (
<div>
{keyword}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>

setstate in async operation you can find your updated state like this
const SearchForm = () => {
const [keyword, setKeyword] = useState('')
const [fetchedData, setFetchedData] = useState([])
async function fetchData() {
const { data } = await axios.post('http://127.0.0.1:8000/api/posts/search/', { keyword });
setFetchedData(data);
}
console.log(fetchedData)
if (redirect) {
return <Redirect to={{ pathname: '/search', data: { fetchedData } }} />
}
return (
)
in your search component console like this :
this.props.location.data

For calling an async function you should use await. So add an IIFE for the async part within the submit handler.
const SearchForm = () => {
const [keyword, setKeyword] = useState('')
const [fetchedData, setFetchedData] = useState([])
const [redirect, setRedirect] = useState(false)
const handleInputChange = ({ target: { value }}) => {
setKeyword(value)
}
const handleSubmit = (e) => {
e.preventDefault()
;(async => {
const { data } = await axios.post(`http...`, { keyword })
setFetchedData(data)
})()
}
if (redirect) {
return <Redirect to={{ pathname: '/search', fetchedData }} />
}
return (
<div>
<form onSubmit={handleSubmit}>
<input onChange={handleInputChange} />
</form>
</div>
)
}

Try this,
function fetchData() {
axios.post('http://127.0.0.1:8000/api/posts/search/', {keyword}).then(data=>{
setFetchedData(data);
console.log(fetchedData)
});

Related

OnChange Event Stuck in Loop, Crashing

I am getting an infinite loop / crash here.
I'm trying to get an onChange event fired for these radio buttons (which are built after pulling data from a query), but I think it keeps redrawing and I can't figure out why.
Any thoughts on how I can solve this?
const GetChallenge = async () => {
const slug = useParams()
const data = await shopifyApolloClient.query({ query: singleProduct(slug) })
return data
}
const Challenge = () => {
let [loaded, setLoaded] = useState(false)
let [product, setProduct] = useState([])
let [variants, setVariants] = useState([])
let [options, setOption] = useState()
let [metafields, setMetafields] = useState([])
GetChallenge().then((ret) => {
setProduct(ret.data.product)
setVariants(ret.data.product.variants.edges)
setOption(ret.data.product.variants.edges[0].node.title)
setMetafields(ret.data.product.metafields.edges)
setLoaded(true)
})
const handleOptions = (event) => {
setOption(event.target.value)
}
if (loaded === true) {
return (
<div>
{variants.map((e) => (
<label
htmlFor={e.node.title}
key={e.node.id}>
<input
type="radio"
name="options"
checked={e.node.title === options}
value={e.node.title}
onChange={handleOptions}
/>
{e.node.title}
</label>
))}
</div>
)
} else {
return (
<p>Not Loaded</p>
)
}
}
GetChallenge is triggering every render. Try useEffect with the empty array empty soas to trigger only onmount.
import React, { useState, useEffect } from 'react';
const GetChallenge = async () => {
...
useEffect(() => {
GetChallenge().then((ret) => {
setProduct(ret.data.product)
setVariants(ret.data.product.variants.edges)
setOption(ret.data.product.variants.edges[0].node.title)
setMetafields(ret.data.product.metafields.edges)
setLoaded(true)
}),[]}
...
}
Try this:
onChange={(event) => setOption(event.target.value)}
hope this will solve your problem :)
import React, { useState, useEffect } from 'react';
const GetChallenge = async () => {
const slug = useParams();
const data = await shopifyApolloClient.query({ query: singleProduct(slug) });
return data;
};
const Challenge = () => {
const [data, setData] = useState({
loaded: false,
product: [],
variants: [],
options: "",
metafields: []
});
const { loaded, variants, options } = data;
useEffect(() => {
GetChallenge().then((ret) => {
setData((prevState) => ({
...prevState,
product: ret.data.product,
variants: ret.data.product.variants.edges,
options: ret.data.product.variants.edges[0].node.title,
metafields: ret.data.product.metafields.edges,
loaded: true
}));
});
}, []);
const handleOptions = (event) => {
setData((prevState) => ({ ...prevState, options: event.target.value }));
};
if (loaded === true) {
return (
<div>
{variants.map((e) => (
<label htmlFor={e.node.title} key={e.node.id}>
<input
type="radio"
name="options"
checked={e.node.title === options}
value={e.node.title}
onChange={(event) => handleOptions(event)}
/>
{e.node.title}
</label>
))}
</div>
);
} else {
return <p>Not Loaded</p>;
}
};

React Hooks setState function is not a function error

I'm getting this error in React Hooks. The function exists but every time I type something in to the search bar I get this TypeError.
TypeError : setSearchField is not a function
Here's the code for reference :
export default function StudentAPI() {
const [searchField, setSearchField] = ('');
const [students, setStudents] = useState([]);
const getStudents = async () => {
return axios
.get("https://api.hatchways.io/assessment/students")
.then((res) => {
setStudents(res.data.students);
})
.catch((err) => console.log(err));
};
useEffect(() => {
getStudents();
}, []);
const handleChange = (e) => {
setSearchField(e.target.value);
}
const filteredStudents = students.filter((student) => {
console.log(student.firstName);
// return student.firstName.toLowerCase().includes(search.toLowerCase()) ||
// student.lastName.toLowerCase().includes(search.toLowerCase());
})
return (
<div className="container">
<SearchBox
placeholder={'Search by name'}
handleChange={handleChange}
value={searchField}
/>
{filteredStudents.length > 0 ? filteredStudents.map((student) => {
return <Student key={student.id} student={student}/>;
}) : students.map((student) => {
return <Student key={student.id} student={student}/>;
})}
</div>
);
};
You have to use the hook useState
const [searchField, setSearchField] = usestate('');
You must have the state declaration above
const [searchField,setSearchField]=useState()
You have an error because useState is not written!
You must change
const [searchField, setSearchField] = ('');
to
const [searchField, setSearchField] = useState('');

Too many re-renders for component

I am trying to call a component that shows the details of a notification when the notification is clicked. However, I kept on getting an error of too many re-renders.
This is my Notifications code
This component calls the database to get the list of notifications and then sets the first notification as the default notification clicked.
const Notification = (hospital) => {
const [users, setUsers] = useState([]);
const [search, setSearch] = useState(null);
const [status, setStatus] = useState(null);
const [notifDetails, setNotification] = useState();
useEffect(async () => {
await axios
.get("/notifications")
.then((res) => {
const result = res.data;
setUsers(result);
setNotification(result[0]);
})
.catch((err) => {
console.error(err);
});
}, []);
return(
<div className="hospital-notif-container">
{filteredList(users, status, search).map((details, index) => {
for (var i = 0; i < details.receiver.length; i++) {
if (
(details.receiver[i].id === hospital.PK ||
details.receiver[i].id === "others") &&
details.sender.id !== hospital.PK
) {
return (
<div
className="hospital-notif-row"
key={index}
onClick={() => setNotification(details)}
>
<div className="hospital-notif-row">
{details.name}
</div>
</div>
);
}
}
return null;
})}
</div>
<NotificationDetails details={notifDetails} />
);
}
For NotificationDetails:
This function is triggered when a notification is clicked from Notifications. The error is said to be coming from this component.
const NotificationDetails = ({ details }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (Object.keys(details).length != 0) {
setLoading(false);
}
}, [details]);
if (!loading) {
return (
<>
<div className="hospital-details-container">
<h2>{details.sender.name}</h2>
</div>
</>
);
} else {return (<div>Loading</div>);}
};
What should I do to limit the re-render? Should I change the second argument of the useEffects call? Or am I missing something in my component?
I tried calling console.log from NotificationDetails and it shows that it is infinitely rendering with the data I set in axios which is result[0]. How is this happening?
Your problem should be in NotificationDetails rendering. You should write something like:
const NotificationDetails = ({ details }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (details.length != 0) {
setLoading(false);
}
}, [details]);
return (
<div>
{loading &&
<div className="hospital-details-container">
<div className="hospital-details-header">
<h2>{details.sender.name}</h2>
</div>
</div>
}
{!loading &&
<div>
<ReactBootStrap.Spinner animation="border" />
</div>
}
</div>
);
}
With return outside the condition statement.
EDIT
Now I noted that you have an async useEffect that is an antipattern. You should modify your useEffect in this way:
useEffect(() => {
(async () => {
await axios
.get("/notifications")
.then((res) => {
const result = res.data;
setUsers(result);
setNotification(result[0]);
})
.catch((err) => {
console.error(err);
});
})()
}, []);

Trigger react useEffect

I'm fetching data from a firebase db it works when the component renders, but I can't make it to fetch again when there is a new entry in my db.
What I've tried
I've tried passing a state to the dependency array of useEffect and I changed that state every time my form was submitted (That's the time when there's a new entry in my db)
App
function App() {
const [showForm, setShowForm] = useState(true);
const [tasks, setTasks] = useState([]);
const [isSubmitted, setIsSubmitted] = useState(true);
//Fetch tasks from server
const fetchData = () => {
fetch(
"https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json"
)
.then((response) => {
return response.json();
})
.then((data) => {
const tasks = [];
//Convert the data to an array so i can map over it
for (const key in data) {
const task = {
id: key,
...data[key],
};
tasks.push(task);
}
setTasks(tasks);
});
};
useEffect(() => {
fetchData();
}, [isSubmitted]);
//Show/Hide form
const onAddHandler = () => {
setShowForm(!showForm);
};
const formSubmitted = () => {
setIsSubmitted(!isSubmitted);
console.log(isSubmitted);
};
return (
<Container>
<Header click={onAddHandler} isShown={showForm}></Header>
{showForm ? <Form fs={formSubmitted}></Form> : ""}
<Tasks tasks={tasks}></Tasks>
</Container>
);
}
export default App;
Form
function Form(props) {
const [task, setTask] = useState();
const [dayTime, setDayTime] = useState();
const [reminder, setReminder] = useState();
//Posting Form data to firebase (DUMMY API)
const postFormData = (fullTask) => {
fetch(
"https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json",
{
method: "POST",
body: JSON.stringify(fullTask),
headers: {
"Content-Type": "application/json",
},
}
);
};
//Make an object of form data
const onSubmit = (e) => {
e.preventDefault();
const fullTask = {
task: task,
dayTime: dayTime,
reminder: reminder,
};
//Post func call
postFormData(fullTask);
props.fs();
//Field clearing
setTask("");
setDayTime("");
setReminder("");
};
return (
<AddForm onSubmit={onSubmit}>
<FormControl>
<Label>Task</Label>
<Input
type="text"
placeholder="Add Task"
onChange={(e) => setTask(e.target.value)}
value={task}
required
></Input>
</FormControl>
<FormControl>
<Label>Day & Time</Label>
<Input
type="text"
placeholder="Add Task"
onChange={(e) => setDayTime(e.target.value)}
value={dayTime}
required
></Input>
</FormControl>
<FromControlCheck>
<CheckLabel>Set Reminder</CheckLabel>
<CheckInput
type="checkbox"
onChange={(e) => setReminder(e.currentTarget.checked)}
value={reminder}
></CheckInput>
</FromControlCheck>
<Submit type="submit" value="Save Task"></Submit>
</AddForm>
);
}
export default Form;
I would pass fetchData as a props to <Form>. When submitted, I would call it.
Form
const onSubmit = async (e) => {
e.preventDefault();
const fullTask = {
task: task,
dayTime: dayTime,
reminder: reminder,
};
//Post func call
await postFormData(fullTask);
await props.fetchData();
//Field clearing
setTask("");
setDayTime("");
setReminder("");
};
Then remove the isSubmitted state.
Try change the "Id" value to "id". Try make it the same name as the key for the id in "fecthData" function.
I think this solve your problem
function App() {
const [showForm, setShowForm] = useState(true);
const [tasks, setTasks] = useState([]);
const [isSubmitted, setIsSubmitted] = useState(false);
//Fetch tasks from server
const fetchData = () => {
fetch(
"https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json"
)
.then((response) => {
return response.json();
})
.then((data) => {
const tasks = [];
//Convert the data to an array so i can map over it
for (const key in data) {
const task = {
id: key,
...data[key],
};
tasks.push(task);
}
setTasks(tasks);
});
};
useEffect(() => {
if (isSubmitted) {
fetchData();
setIsSubmitted(false);
}
}, [isSubmitted]);
//Show/Hide form
const onAddHandler = () => {
setShowForm(!showForm);
};
const formSubmitted = () => {
setIsSubmitted(true);
console.log(isSubmitted);
};
return (
<Container>
<Header click={onAddHandler} isShown={showForm}></Header>
{showForm ? <Form fs={formSubmitted}></Form> : ""}
<Tasks tasks={tasks}></Tasks>
</Container>
);
}
export default App;

NextJS how to fetch data after click event?

I have problem with load data to component after click on button.
I use getInitialProps to first load data on page.
How to load new data and past them to {data} after click?
export default function Users({ data }) {
const fetchData = async () => {
const req = await fetch("https://randomuser.me/api/?gender=male&results=100");
const data = await req.json();
return { data: data.results };
};
const handleClick = (event) => {
event.preventDefault();
fetchData();
};
return (
<Layout>
<button onClick={handleClick}>FETCH DATA</button>
{data.map((user) => {
return (
<div>
{user.email}
<img src={user.picture.medium} alt="" />
</div>
);
})}
</Layout>
);
}
Users.getInitialProps = async () => {
const req = await fetch(
"https://randomuser.me/api/?gender=female&results=10"
);
const data = await req.json();
return { data: data.results };
};
Thank a lot for help!
Use useState with the default value being the data you initially retrieved via getInitialProps:
import { useState } from 'React';
export default function Users({ initialData }) {
const [data, setData] = useState(initialData);
const fetchData = async () => {
const req = await fetch('https://randomuser.me/api/?gender=male&results=100');
const newData = await req.json();
return setData(newData.results);
};
const handleClick = (event) => {
event.preventDefault();
fetchData();
};
return (
<Layout>
<button onClick={handleClick}>FETCH DATA</button>
{data.map((user) => {
return (
<div>
{user.email}
<img src={user.picture.medium} alt="" />
</div>
);
})}
</Layout>
);
}
Users.getInitialProps = async () => {
const req = await fetch('https://randomuser.me/api/?gender=female&results=10');
const data = await req.json();
return { initialData: data.results };
};
Sidenote: Times have changed and it would seem that user1665355 is indeed correct:
Recommended: getStaticProps or getServerSideProps
If you're using Next.js 9.3 or newer, we recommend that you use
getStaticProps or getServerSideProps instead of getInitialProps.
These new data fetching methods allow you to have a granular choice
between static generation and server-side rendering.
import { useState } from 'React';
export default function Users({ initialData }) {
const [data, setData] = useState(initialData);
const fetchData = async () => {
const req = await fetch('https://randomuser.me/api/?gender=male&results=100');
const newData = await req.json();
setData(newData.results);
};
const handleClick = (event) => {
event.preventDefault();
fetchData();
};
return (
<Layout>
<button onClick={handleClick}>FETCH DATA</button>
{data.map(user => {
return (
<div key={user.login.uuid}>
{user.email}
<img src={user.picture.medium} alt="" />
</div>
);
})}
</Layout>
);
}
Users.getInitialProps = async () => {
const req = await fetch('https://randomuser.me/api/?gender=female&results=10');
const data = await req.json();
return { initialData: data.results };
};
I would like to list my notes about George's code. At least, it should pay attention to them.
First of all, it should attach any key to a div element otherwise a warning will have appeared in the browser console. Here is an article about using keys: https://reactjs.org/docs/lists-and-keys.html#keys
As well, the keyword return can be removed from the fetchData function that doesn't return a response.
It is recommended to use getStaticProps or getServerSideProps now. https://nextjs.org/docs/api-reference/data-fetching/getInitialProps

Resources