I am building a Simple ToDoList App.
I fetch List Using React Hook.
When I add a new Todo or delete an existing one the request sends and works but the component doesn`t rerender.
I tried 2 ways to solve the problem to create
1.async functions(delete and add). Took getToDoList outside the hook and call it after requests(post/delete)
useEffect(() => {
getToDoList();
}, []);
const getToDoList = async () => {
const result = await axios.get('http://localhost:1200/');
setToDoList(result.data);
};
const addNewTodo = async () => {
await axios.post('http://localhost:1200/create', {
item: newToDo.current.value
});
getToDoList();
};
const deleteToDo = async (id) => {
await axios.delete(`http://localhost:1200/delete?id=${id}`);
getToDoList();
};
2.Took getToDoList inside the hook and gave it 2 dependecies which change in deleteToDo/addNewToDo
const [add, setAdd] = useState(false);
const [remove, setRemove] = useState(false);
useEffect(() => {
const getToDoList = async () => {
const result = await axios.get('http://localhost:1200/');
setToDoList(result.data);
};
getToDoList();
}, [add, remove]);
const addNewTodo = async () => {
await axios.post('http://localhost:1200/create', {
item: newToDo.current.value
});
setAdd(!add);
};
const deleteToDo = async (id) => {
await axios.delete(`http://localhost:1200/delete?id=${id}`);
setRemove(!remove);
};
Both don`t work. Tell me please where Im wrong
JSX
return (
<div>
<Jumbotron>
<Container>
<h1>Hello!</h1>
<p>This is a simple ToDoList created by Vadik</p>
</Container>
</Jumbotron>
<Container>
<Container>
<InputGroup>
<FormControl placeholder="What needs to be done" ref={newToDo}/>
</InputGroup>
<Button onClick={addNewTodo} variant="primary" size="sm">
Add new Todo
</Button>
</Container>
<Container className="cards">
<Card>
<ListGroup>
{toDoList.todos.map((elem) => <ListGroup.Item
key={elem._id}>{elem.item}<CloseButton onClick={() => deleteToDo(elem._id)}/> </ListGroup.Item>)}
</ListGroup>
</Card>
</Container>
</Container>
</div>
);
Try the below code.This should work.In addTodo there are 2 ways either update the resultList after post is successful or use the getList to get the latest data and update the resultList. Both of them are shown.
function Example() {
const [resultList, setResult] = useState({todos: []});
useEffect(() => {
const todoList = getToDoList();
setResult(prev => todoList);
}, []);
const addTodo = async () => {
await axios.post('http://localhost:1200/create', {
item: newToDo.current.value
});
//Addition successful, hence update our resultList.
return setResult(prev => {
return {todos: [...prev.todos, {item: newToDo.current.value, id: prev.todos.length+1}]}
});
//Now since add api doesn't return a
//value call Get api again and update the todoList to render
//const todoList = getToDoList();
//setResult(prev => todoList);
};
const getToDoList = async () => {
const result = await axios.get('http://localhost:1200/');
return result.data;
};
return (
<div id='container'>
{resultList.todos.map(todo => <div>{todo.item}</div>
)}
<button onClick={addTodo}>Add</button>
</div>
)
}
The problem was that I didn`t send any response to those requests.enter image description here
Related
I followed this approach of embedding a hubspot form on a react component
HubspotForm.tsx
import React, { useEffect } from 'react'
import './HubspotForm.scss'
const HubspotForm = () => {
useEffect(() => {
const script = document.createElement('script')
script.src = 'https://js.hsforms.net/forms/v2.js'
document.body.appendChild(script)
script.addEventListener('load', () => {
// #TS-ignore
if (window.hbspt) {
// #TS-ignore
window.hbspt.forms.create({
portalId: '*****',
formId: '*****',
target: '#hubspotForm',
//onFormSubmit: function ($form) {},
})
}
})
}, [])
return (
<div className="form-container">
<form id="hubspotForm" />
</div>
)
}
export default HubspotForm
I need to be able to add a onSubmit event listener inside the form and pass on a function inside it
Ideally:
App.tsx
const App = () => {
const [isSuccessShown, setIsSuccessShown] = useState<boolean>(false)
const handleSubmit = (): void => {setIsSuccessShown(true)}
return (
<>
<HubspotForm
//onSubmit={handleSubmit} ==> I need this to be added
/>
</>
);
};
I tried different approaches to add onSubmit event listener but none of them seems to work
Receive onSubmit in your HubspotForm.tsx, like so:
const HubspotForm = ({onSubmit}) => {
// now you can call onSubmit, which points to handleSubmit
}
Don't forget to uncomment the line in App.tsx
Added the following code for onFormSubmit and seems to work just fine
const HubspotForm = ({onSubmit}) => {
useEffect(() => {
const script = document.createElement('script')
script.src = 'https://js.hsforms.net/forms/v2.js'
document.body.appendChild(script)
script.addEventListener('load', () => {
// #TS-ignore
if (window.hbspt) {
// #TS-ignore
window.hbspt.forms.create({
portalId: '*****',
formId: '*****',
target: '#hubspotForm',
onFormSubmit: function ($form) {
var formData = $form.serializeArray()
onSubmit(formData)
},
})
}
})
}, [])
return (
<div className="form-container">
<form id="hubspotForm" />
</div>
)
}
for learning purposes I'm creating a CRUD todo list with React and JSON-server. I got stuck with PATCH method, as it only updates data in JSON-server on the first click. I want to update the data with the component's state value.
Service file with requests:
const serverAddress = 'http://localhost:8000';
const collection = 'todoItems';
const fetchAll = async () => {
const response = await fetch(`${serverAddress}/${collection}`);
const todoItems = response.json();
return todoItems;
};
const complete = async (id) => {
const completed = {
completed : true
}
// how to set the 'completed' value in json-server based on item's state?
const response = await fetch(`${serverAddress}/${collection}/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(completed ),
});
const data = await response.json();
return data;
}
const TodoItemsService = {
fetchAll,
create,
remove,
complete
};
export default TodoItemsService;
Card component which holds all todo items:
const Card = () => {
const [todoItems, setTodoItems] = useState([]);
const fetchAllTodoItems = async () => {
const fetchedTodoItems = await TodoItemsService.fetchAll();
setTodoItems(fetchedTodoItems);
};
useEffect(() => {
(async () => {
fetchAllTodoItems();
})();
}, []);
const handleComplete = async (id) => {
await TodoItemsService.complete(id);
}
return (
<div className='card'>
<CardHeader />
<AddTodoForm onAddTodoItem={handleAddTodoItem} />
<TodoItemsContainer
todoItems={todoItems}
onDelete={handleDelete}
onComplete={handleComplete}
/>
</div>
)
}
export default Card;
TodoItemsContainer component
const TodoItemsContainer = ({ todoItems, onDelete, onComplete }) => {
return (
<div className='todo-items-container'>
{todoItems.length === 0 &&
<div className='empty'>
<img src={NoTodoItems} alt="" />
</div>}
{todoItems.map(({ id, text }) => (
<TodoItem
key={id}
id={id}
text={text}
onDelete={onDelete}
onComplete={onComplete}
/>
))}
</div>
)
}
export default TodoItemsContainer;
TodoItem component
const TodoItem = ({ id, text, onDelete, onComplete }) => {
const [isComplete, setIsComplete] = useState(false);
const handleIsCompleteById = () => {
onComplete(id);
setIsComplete(!isComplete);
};
const handleDeleteTodoItemById = () => {
onDelete(id);
};
return (
<div className={`todo-item ${isComplete ? 'complete' : ''}`}>
<p>{text}</p>
<div>
<TodoItemComplete onComplete={handleIsCompleteById}/>
<TodoItemDelete onDelete={handleDeleteTodoItemById}/>
</div>
</div>
)
}
export default TodoItem;
TodoItemComplete button component
const TodoItemComplete = ({ onComplete }) => {
return (
<button type='button' onClick={onComplete}>
<div className='icon'>
{<SVGComplete />}
</div>
</button>
)
}
export default TodoItemComplete;
From React perspective it works fine, it marks the item as complete based on state, but I also want to reflect todo item's status as complete in my json-server. Does anyone have any tips or can see the mistake?
Simply had to pass the state as the second param in complete service and other functions that handle complete action.
import { useState, useEffect } from 'react';
import axios from 'axios'
import { Loading } from './loading';
function News({ pageSize }) {
const [isLoading, setIsLoading] = useState(false)
const [state, setState] = useState({
article: [],
page: 1
}
)
const getUsers = async () => {
setIsLoading(true)
let res = await axios.get(`https://newsapi.org/v2/everything?domains=wsj.com&apiKey=79b02b430c1946cd9c505d3f91d7aec6&page=1&pageSize=${pageSize}`);
setState({article: res.data.articles})
setIsLoading(false)
};
useEffect(() => {
getUsers()
}, [])
const handleNext = async () => {
setIsLoading(true)
let res = await axios.get(`https://newsapi.org/v2/everything?domains=wsj.com&apiKey=79b02b430c1946cd9c505d3f91d7aec6&page=${state.page + 1}&pageSize=${pageSize}`);
setState({article: res.data.articles, page: state.page + 1})
setIsLoading(false)
}
let data = Array.from(state.article)
return (
<div>
<h2>News</h2>
<button onClick={handleNext}>Next</button>
{isLoading && <Loading />}
{!isLoading && data.map((elements) => {
return (
<div key={elements.url} style={{ marginBottom: '2rem' }}>
<div> {elements.description} </div>
<div>{new Date(elements.publishedAt).toGMTString()}</div>
</div>
)
})}
</div>
);
}
export default News;
When I take states separately for data and page, I'm able to display next page's data. But now that I've created one state to manage multiple objects, it displays back first page's data instead of next page's data. I don't know what I'm doing wrong. Pls help me!
Ignore the redundancy.
Try this:
const getUsers = async () => {
setIsLoading(true)
let res = await axios.get(`https://newsapi.org/v2/everything?domains=wsj.com&apiKey=79b02b430c1946cd9c505d3f91d7aec6&page=1&pageSize=${pageSize}`);
setState({...state, article: res.data.articles})
setIsLoading(false)
};
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;
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