I have a parent Component named Posts that has a useEffect and fetches data, then I have a child component named Post which renders the post from props. I have a delete and like function inside Post and I want the parent to rerender when post get liked or post gets deleted. How can I achieve this?
const Posts = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
getPosts();
}, []);
const getPosts = async () => {
const data = await fetch('https://localhost:1111/api/posts/posts', {
credentials: 'include'
});
const response = await data.json();
console.log(response);
setPosts(response);
};
return (
<div>
{posts.map(post => (
<Post key={post.id} post={post} />
))}
</div>
);
};
const Post = props => {
const like = async post => {
const formData = new FormData();
formData.append('id', post);
const data = await fetch('https://localhost:1111/api/posts/like', {
method: 'POST',
body: formData,
credentials: 'include'
});
const response = await data.json();
console.log(response);
};
const deletePost = async post => {
const formData = new FormData();
formData.append('id', post);
const data = await fetch('https://localhost:1111/api/posts/deletepost', {
method: 'POST',
body: formData,
credentials: 'include'
});
const response = await data.json();
console.log(response);
};
return (
<div>
<img
src={`http://localhost:1111/api/posts/uploads/images/${props.post.content}`}
alt={`Post ${props.post.id}`}
/>
<button onClick={() => like(props.post.id)}>Like</button>
<button onClick={() => deletePost(props.post.id)}>Delete</button>
</div>
);
};
I would recommend you to have an additional prop method (onUpdate) on the child <Post /> component, which gets triggered whenever the deletePost() or like() are triggered.
const Posts = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
getPosts();
}, []);
const getPosts = async () => {
const data = await fetch('https://localhost:1111/api/posts/posts', {
credentials: 'include'
});
const response = await data.json();
console.log(response);
setPosts(response);
};
const handleUpdate = () => {
getPosts();
}
return (
<div>
{posts.map(post => (
<Post key={post.id} onUpdate={() => handleUpdate()} post={post} />
))}
</div>
);
};
const Post = props => {
const { onUpdate } = props;
const like = async post => {
const formData = new FormData();
formData.append('id', post);
const data = await fetch('https://localhost:1111/api/posts/like', {
method: 'POST',
body: formData,
credentials: 'include'
});
const response = await data.json();
console.log(response);
onUpdate();
};
const deletePost = async post => {
const formData = new FormData();
formData.append('id', post);
const data = await fetch('https://localhost:1111/api/posts/deletepost', {
method: 'POST',
body: formData,
credentials: 'include'
});
const response = await data.json();
console.log(response);
onUpdate();
};
return (
<div>
<img
src={`http://localhost:1111/api/posts/uploads/images/${props.post.content}`}
alt={`Post ${props.post.id}`}
/>
<button onClick={() => like(props.post.id)}>Like</button>
<button onClick={() => deletePost(props.post.id)}>Delete</button>
</div>
);
};
As you can see, onUpdate() is called at the end of both deletePost() and like(), and on the parent Posts component, handleUpdate() will be called, which in turn submits the fetch response to update the posts state. This will cause the parent <Posts /> to re-render.
Keep post functionality inside the parent component and pass the update functions to Post
const Posts = () => {
const [posts, setPosts] = useState([])
useEffect(() => {
fetch('https://localhost:1111/api/posts/posts', {
credentials: 'include',
})
.then(res => res.json())
.then(setPosts)
}, [])
const likePost = async postId => {
await fetch...
setPosts(prevState =>
prevState.map(post =>
post.id === postId
? {
...post,
likes: post.likes + 1,
}
: post
)
)
}
const deletePost = async postId => {
await fetch...
setPosts(prevState => prevState.filter(post => post.id !== postId))
}
return (
<div>
{posts.map(post => (
<Post
key={post.id}
post={post}
likePost={likePost}
deletePost={deletePost}
/>
))}
</div>
)
}
const Post = ({ post, likePost, deletePost }) => (
<div>
<img
src={`http://localhost:1111/api/posts/uploads/images/${post.content}`}
alt={`Post ${post.id}`}
/>
<button onClick={() => likePost(post.id)}>Like</button>
<button onClick={() => deletePost(post.id)}>Delete</button>
</div>
)
Related
I have two components.
DropDownForRoomChangeCondo.js js that displays radio buttons. DiscoverCondoRoom.js displays DropDownForRoomChangeCondo.js.
What I want to achieve is In DropDownForRoomChangeCondo.js, When sending a request to the backend with handleChange (when switching the radio button) I want to change the screen display by calling getDevices(); in DiscoverCondoRoom.js. (Since the assignment of the room where the device is installed changes when the radio button is switched, I want to update the display)
Issue/error message
Currently, when sending a request to the backend with handleChange (when switching the radio button) Display update does not occur.
DropDownForRoomChangeCondo.js
import Dropdown from 'react-bootstrap/Dropdown';
const DropDownForRoomChangeCondo = (item) => {
const history = useHistory();
const [devices, setDevices] = useState([]);
const handleChange = e => {
setVal(e.target.name);
setDeviceRoomName(e.target.name);
}
const setDeviceRoomName = async(data) => {
console.log("Body sent to server", {
attributes:
[
{
entity_id : item.item.entity_id,
room_name: data
}
]
})
await axios.post('xxx.com',
{
attributes:
[
{
entity_id : item.item.entity_id,
room_name: data
}
]
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
console.log('Set Device Room Name!');
getDevices();
})
.catch(err => {
console.log(err);
console.log('Missed Set Device Room Name!');
});
}
const getDevices = async(data) => {
await axios.get('xxx.com',
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
console.log(result.data)
console.log("bbbbbbbbbbb")
setDevices(result.data.attributes);
})
.catch(err => {
console.log(err);
});
}
const keys = [
"camera",
"climate",
"cover",
"light",
"lock",
"sensor",
"switch",
];
const entities = keys
.map((key) => (devices[key] || []).map((e) => ({ ...e, key })))
.flat();
const roomNames = [...new Set(entities.map((entity) => entity.room_name))];
const [val, setVal] = useState(item.item.room_name);
console.log(val)
console.log(typeof(val))
const CustomToggle = React.forwardRef(({ children, onClick }, ref) => (
<a
href=""
ref={ref}
onClick={(e) => {
e.preventDefault();
onClick(e);
}}
>
{children}
<img className="ic_edit" src={ic_edit} />
</a>
));
useEffect(() => {
getDevices();
},[]);
return (
<>
<div className="">
<p>{item.item.room_name}</p>
<Dropdown className="room_change_dropdown_top">
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" />
<Dropdown.Menu className="room_change_dropdown">
<Dropdown.Item className="room_change_dropdown_item">
{roomNames.map((room_names, i) => (
<div className="flex_radio">
<input
className="room_change_radio"
type="radio"
value={room_names}
name={room_names}
onChange={handleChange}
checked={val === room_names}
/>
<p className="drop_down_p">{room_names}</p>
</div>
))}
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
</>
);
}
export default DropDownForRoomChangeCondo;
DiscoverCondoRoom.js
const DiscoverCondoRoom = () => {
const history = useHistory();
const [devices, setDevices] = useState([]);
const getDevices = async(data) => {
await axios.get('xxx.com',
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
setDevices(result.data.attributes);
})
.catch(err => {
console.log(err);
});
}
useEffect(() => {
getDevices();
},[]);
const lll = Object.keys(devices);
const object_device_value = Object.values(devices).flat();
const keys = [
"camera",
"climate",
"cover",
"light",
"lock",
"sensor",
"switch",
];
const entities = keys
.map((key) => (devices[key] || []).map((e) => ({ ...e, key })))
.flat();
const roomNames = [...new Set(entities.map((entity) => entity.room_name))];
return (
<>
<div className="container condo_container">
{entities.map((entity, i) => (
<DropDownForRoomChangeCondo item={entity} />
))}
</div>
</>
);
}
};
export default DiscoverCondoRoom;
You need to pass your getDevices Method as a prop to your dropdown component.
<DropDownForRoomChangeCondo item={entity} getDevices={getDevices} />
Then inside your DropDown Component, call the getDevices Method at your desired place by calling props.getDevices().
Also, i would suggest to define props like so:
const DropDownForRoomChangeCondo = (props) => {
const history = useHistory();
const [devices, setDevices] = useState([]);
…
And then access item by pointing to props.item
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.
My parent component use hook useEffect for get data from API and pass props to child component.
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
useEffect(() => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return <Child adsData={adsData} />;
};
My Child component has handleDeleteClick function for delete request from API
const Child = () => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => console.log(res))
.catch((err) => console.log(err));
};
return (
<div>
// array.map Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};
Delete request works successfully, but my list not updated.
How update my items list after deleted item?
You would need to pass another function that is called when a delete is executed. Something like:
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
const fetchData = () => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
};
const onDelete = () => {
fetchData();
};
useEffect(() => {
fetchData();
}, []);
return <Child adsData={adsData} onDelete={fetchData} />;
};
const Child = (props) => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => {
console.log(res);
props.onDelete();
})
.catch((err) => console.log(err));
};
return (
<div>
// Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};
Put your delete function in the parent and pass it to the child. Then after deleting, update your list in the parent.
<ParentComponent>
const [adsData, setAdsData] = useState([]);
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, {params: {id: id}})
.then(res => {
console.log(res)
//TODO:: Implement list.pop or similar
})
.catch(err => console.log(err));
};
useEffect(() => {
api.get(`MyUrl`, { headers: authHeader() })
.then(res => {
console.log(res);
setAdsData(res.data.data);
})
.catch(err => {
console.log(err);
})
}, []);
return (
<Child
adsData={adsData}
handleClick={handleDeleteClick}
/>
)
</ParentComponent>
return (
<div>
// array.map Items list
<a href="" onClick={(e) =>
handleDeleteClick(e, ads.id)}className="tables__link">Delete</a>
</div>
)```
Alternating between the 2 buttons will display first names or last names, but pressing them together really fast will chain requests and will combine the two. How can I make create a check, and only display the names from the button that was pressed last
export default function App() {
const [name, setName] = useState();
return (
<div className="App">
<button onClick={() => setName("first_name")}>1</button>
<button onClick={() => setName("last_name")}>2</button>
<Users name={name} />
</div>
);
}
export default function Users({ name }) {
const [users, setUsers] = useState([]);
useEffect(() => {
setUsers([]);
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers((prev) => [...prev, ...allUsers]);
})
.catch((e) => {
console.log(e);
});
}, [name]);
return <div className="Users">{users}</div>;
}
Here is a great article by Dan Abramov about the useEffect hook in which he also talks about how to handle race cases- https://overreacted.io/a-complete-guide-to-useeffect/#speaking-of-race-conditions
To solve your issue, create a variable like let didCancel = false at the start of useEffect. Then, you have to return a function from useEffect, which automatically runs at the time when the name changes next time. In that function set didCancel to true. Now, you have to handle fetch response only if didCancel is false. This way, you are discarding all fetch responses received from second-last, third-last, etc. button presses, and handling fetch response only from the last button press.
Here is updated useEffect code:-
useEffect(() => {
let didCancel = false;
setUsers([]);
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
if (!didCancel) {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers((prev) => [...prev, ...allUsers]);
}
})
.catch((e) => {
console.log(e);
});
return () => {
didCancel = true;
};
}, [name]);
return <div className="Users">{users}</div>;
}
you have to create a loading state, and the user should not be able to send a new request until the data is received... you can create a hook for this or use SWR:
let me give you an example:
function Users(usersList) {
return (
<ul>
{usersList.map((user, key) => (
<li key={key}>{user}</li>
))}
</ul>
);
}
const useFetchUsers = (name) => {
const [isLoading, setIsLoading] = React.useState(true);
const [error, setError] = React.useState(null);
const [data, setData] = React.useState([]);
React.useEffect(() => {
setIsLoading(true);
setError(null);
fetch('https://blahblahblah.com/api/users')
.then((res) => res.json())
.then((response) => setData(response))
.catch((err) => setError(err))
.finally(() => setIsLoading(false));
}, [name]);
return {
isLoading,
error,
data,
};
};
function App() {
const [name, setName] = React.useState('Tom');
const { isLoading, error, data } = useFetchUsers(name);
const handleSubmitName = (name) => {
if (isLoading) alert('wait!');
else setName(name);
};
if (error) return <>an error occured</>;
if (data)
return (
<>
<button onClick={() => handleSubmitName('first_name')}>1</button>
<button onClick={() => handleSubmitName('last_name')}>2</button>
<Users name={name} />
</>
);
}
hint/note: it's just pseudocode and there are some tools to do data fetching + caching.
The problem is in this line setUsers((prev) => [...prev, ...allUsers]);. You are assuming that prev is [], but when the second request is resolve prev has data, that is why you see the request are combined:
I recommend to change your useEffect block to avoid the problem you are facing:
useEffect(() => {
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers(...allUsers); //--> with the last name's value
})
.catch((e) => {
console.log(e);
});
}, [name]);
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