React | Why the defaultValue of the input isn't being updated? - node.js

I have a stateful component that calls a CEP promise, to fetch data from post offices. This data is fetched when the Zip input is fulfilled with 9 chars - 8 number and an '-' - and return an object with desired information.
Heres the function:
const handleZipCode = useCallback(
async ({ target }: ChangeEvent<HTMLInputElement>) => {
const { value } = target;
try {
if (value.length === 9 && isDigit(value[8])) {
const zip = await cep(value);
if (zip?.city) {
setZipData(zip);
} else
addressFormRef.current?.setErrors({
user_zip_code: 'CEP not found',
});
}
} catch (e) {
addressFormRef.current?.setErrors({
user_zip_code: e.message ?? 'CEP not found',
});
}
},
[isDigit]
);
Then, on the return I have some fields, example:
<fieldset>
<legend>Address</legend>
<Input
mask=''
name='user_address'
placeholder='Rua um dois três'
defaultValue={zipData.street}
/>
</fieldset>
Here's the Input component:
const Input: React.FC<InputProps> = ({ name, ...rest }) => {
const { fieldName, defaultValue, registerField, error } = useField(name);
const inputRef = useRef(null);
useEffect(() => {
registerField({
name: fieldName,
ref: inputRef.current,
path: 'value',
// eslint-disable-next-line
setValue(ref: any, value: string) {
ref.setInputValue(value);
},
// eslint-disable-next-line
clearValue(ref: any) {
ref.setInputValue('');
},
});
}, [fieldName, registerField]);
return (
<Container>
<ReactInputMask ref={inputRef} defaultValue={defaultValue} {...rest} />
{error && <Error>{error}</Error>}
</Container>
);
};
However the zipData seems to update, but the default value is not fulfilled. What I'm doing wrong?

The default value will not change, as unform is an uncontrolled form library, the defaultValue will be set on the first render of the page and then will not change anymore.
To fix your problem you can do something like:
// on your handleZipCode function
formRef.current.setData({
zipData: {
street: zipResult.street,
},
});

Related

Getting nested array in checkbox input

I am trying to get the nested array from a input value of a checkbox.
How do I handle a nested array?
These are the values:
const othersOptions = [
{procedure:'ORAL PROPHYLAXIS',price: 1000},
{procedure:'TOOTH RESTORATION',price:1200},
{procedure:'TOOTH EXTRACTION',price:800}
];
This is how I get the values from checkbox. I am guessing that value={[item]} is procedure:'ORAL PROPHYLAXIS',price: 1000 if the ORAL PROPHYLAXIS checkbox is checked
<Form>
{othersOptions.map((item, index) => (
<div key={index} className="mb-3">
<Form.Check
value={[item]}
id={[item.procedure]}
type="checkbox"
label={`${item.procedure}`}
onClick={handleChangeCheckbox('Others')}
required
/>
</div>
))}
</Form>
When I console.log the value it shows that the value is [Object object] this is the value.
const handleChangeCheckbox = input => event => {
var value = event.target.value;
console.log(value, "this is the value")
var isChecked = event.target.checked;
setChecked(current =>
current.map(obj => {
if (obj.option === input) {
if(isChecked){
return {...obj, chosen: [{...obj.chosen, value}] };
}else{
var newArr = obj.chosen;
var index = newArr.indexOf(event.target.value);
newArr.splice(index, 1);
return {...obj, chosen: newArr};
}
}
return obj;
}),
);
console.log(checked);
}
and this is how I save the nested array:
const [checked, setChecked] = useState([
{ option: 'Others',
chosen: [],
]);
The reason why I need the procedure and price is so that I can save the values to MongoDB and get the values to another page which is a Create Receipt page. I want the following procedures price to automatically display in the Create Receipt page.Thank you for the help!
If anyone is wondering how I fixed it.
I stringfy the input values and parsed the e.target.values
import "./styles.css";
import { Form } from "react-bootstrap";
import { useState } from "react";
import React from "react";
const othersOptions = [
{ procedure: "ORAL PROPHYLAXIS", price: 1000 },
{ procedure: "TOOTH RESTORATION", price: 1200 },
{ procedure: "TOOTH EXTRACTION", price: 800 },
{ procedure: "DEEP SCALING", price: 10200 },
{ procedure: "PTS AND FISSURES SEALANT", price: 700 },
{ procedure: "FLOURIDE TREATMENT", price: 5500 },
{ procedure: "INTERMEDIATE RESTORATION", price: 7000 },
{ procedure: "ORTHODONTICS", price: 48000 }
];
export default function App() {
const [checked, setChecked] = useState([{ option: "Others", chosen: [] }]);
console.log(checked);
const handleChangeCheckbox = (input) => (event) => {
var value = JSON.parse(event.target.value);
var isChecked = event.target.checked;
console.log("value is:", value[0].procedure);
var tempArr = { procedure: value[0].procedure, price: value[0].price };
setChecked((current) =>
current.map((obj) => {
if (obj.option === input) {
if (isChecked) {
return { ...obj, chosen: [...obj.chosen, tempArr] };
} else {
var newArr = obj.chosen;
var index = newArr.indexOf(event.target.value);
newArr.splice(index, 1); // 2nd parameter means remove one item only
return { ...obj, chosen: newArr };
}
}
return obj;
})
);
};
return (
<Form>
{othersOptions.map((item, index) => (
<div key={index} className="mb-3">
<Form.Check
value={JSON.stringify([item])}
id={[item]}
type="checkbox"
label={`${item.procedure}`}
onClick={handleChangeCheckbox("Others")}
required
/>
</div>
))}
</Form>
);
}
RUN THE CODE HERE

How to make reusable pagination with react and redux?

On my application i'm using Reduxjs/toolkit for state management and TypeScript for type safety.
My backend were wrote in Node.js with MongoDB.
I have implemented pagination for one slice/component, and i know that is not the best solution and i want to improve it and make reusable for other slices.
Could you help me with that? Give me some hints?
Below is my current pagination implementation:
// CategorySlice
interface InitialState {
categories: ICategory[];
isFetching: boolean;
errorMessage: string | null;
// to refactor
currentPage: number;
itemsPerPage: number;
totalResults: number;
}
const initialState: InitialState = {
categories: [],
isFetching: false,
errorMessage: '',
// to refactor
currentPage: 1,
itemsPerPage: 9,
totalResults: 0,
};
export const fetchCategories = createAsyncThunk<
{ data: ICategory[]; totalResults: number },
number
>('category/fetchCategories', async (currentPage, { rejectWithValue }) => {
try {
const accessToken = getToken();
if (!accessToken) rejectWithValue('Invalid token');
const config = {
headers: { Authorization: `Bearer ${accessToken}` },
};
const response: IApiResponse<ICategoryToConvert[]> = await api.get(
`/categories?page=${currentPage}&limit=9&isPrivate[ne]=true`,
config
);
const data = response.data.data;
const convertedData = data.map(e => {
return {
id: e._id,
name: e.name,
image: e.image,
};
});
return {
totalResults: response.data.totalResults,
data: convertedData,
};
} catch (error) {
removeToken();
return rejectWithValue(error);
}
});
export const categorySlice = createSlice({
name: 'category',
initialState,
reducers: {
setNextPage(state, { payload }) {
state.currentPage = payload;
},
},
extraReducers: builder => {
builder.addCase(fetchCategories.pending, state => {
state.isFetching = true;
state.errorMessage = null;
});
builder.addCase(fetchCategories.fulfilled, (state, action) => {
state.categories = action.payload.data;
state.isFetching = false;
state.totalResults = action.payload.totalResults;
});
builder.addCase(fetchCategories.rejected, state => {
state.isFetching = false;
state.errorMessage = 'Problem with fetching categories 🐱‍👤';
});
},
});
// Category Page
const CategoryPage = () => {
const dispatch = useAppDispatch();
const { currentPage } = useAppSelector(state => state.category);
useEffect(() => {
dispatch(fetchCategories(currentPage));
}, [dispatch, currentPage]);
return (
<ContainerWrapper>
<CategoryList />
</ContainerWrapper>
);
};
export default CategoryPage;
Inside CategoryPage
I'm passing those properties from state selector.
<Pagination
currentPage={currentPage}
itemsPerPage={itemsPerPage}
paginate={(n: number) => dispatch(categoryActions.setNextPage(n))}
totalItems={totalResults}
/>
And finally PaginationComponent
interface IProps {
itemsPerPage: number;
totalItems: number;
paginate: (numb: number) => void;
currentPage: number;
}
const Pagination = ({ itemsPerPage, totalItems, paginate, currentPage }: IProps) => {
const numberOfPages = [];
for (let i = 1; i <= Math.ceil(totalItems / itemsPerPage); i++) {
numberOfPages.push(i);
}
return (
<nav className={styles['pagination']}>
<ul className={styles['pagination__list']}>
{numberOfPages.map(number => {
return (
<li
key={number}
className={`${styles['pagination__item']} ${
currentPage === number && styles['pagination__item--active']
}`}
onClick={() => paginate(number)}
>
<div className={styles['pagination__link']}>{number}</div>
</li>
);
})}
</ul>
</nav>
);
};
export default Pagination;

Why do I have to refresh the page when I delete a post? MERN stack

I am a beginner in the MERN stack and I am interested in why I have to refresh the page after deleting the document (post)?
This is my Action.js
export const deletePost = id => async (dispatch, getState) => {
try {
dispatch({ type: DELETE_POST_BEGIN });
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.delete(`/api/v1/post/${id}`, config);
dispatch({ type: DELETE_POST_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: DELETE_POST_FAIL,
payload: { msg: error.response.data.msg },
});
}
};
This is my Reducer.js
export const deletePostReducer = (state = {}, action) => {
switch (action.type) {
case DELETE_POST_BEGIN:
return { loading: true };
case DELETE_POST_SUCCESS:
return { loading: false };
case DELETE_POST_FAIL:
return { loading: false, error: action.payload.msg };
default:
return state;
}
};
And this is my Home page where i list all posts:
import { useEffect } from 'react';
import { Col, Container, Row } from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { getPosts } from '../actions/postActions';
import Loader from '../components/Loader';
import Message from '../components/Message';
import Post from '../components/Post';
const HomePage = () => {
const dispatch = useDispatch();
const allPosts = useSelector(state => state.getPosts);
const { loading, error, posts } = allPosts;
const deletePost = useSelector(state => state.deletePost);
const { loading: loadingDelete } = deletePost;
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
return (
<Container>
{loading || loadingDelete ? (
<Loader />
) : error ? (
<Message variant='danger'>{error}</Message>
) : (
<>
<Row>
{posts.map(post => (
<Col lg={4} key={post._id} className='mb-3'>
<Post post={post} />
</Col>
))}
</Row>
</>
)}
</Container>
);
};
export default HomePage;
And this is my single Post component:
const Post = ({ post }) => {
const dispatch = useDispatch();
const allPosts = useSelector(state => state.getPosts);
const { loading, error, posts } = allPosts;
const userLogin = useSelector(state => state.userLogin);
const { userInfo } = userLogin;
const handleDelete = id => {
dispatch(deletePost(id));
};
return (
<>
<div>{post.author.username}</div>
<Card>
<Card.Img variant='top' />
<Card.Body>
<Card.Title>{post.title}</Card.Title>
<Card.Text>{post.content}</Card.Text>
<Button variant='primary'>Read more</Button>
{userInfo?.user._id == post.author._id && (
<Button variant='danger' onClick={() => handleDelete(post._id)}>
Delete
</Button>
)}
</Card.Body>
</Card>
</>
);
};
And my controller:
const deletePost = async (req, res) => {
const postId = req.params.id;
const post = await Post.findOne({ _id: postId });
if (!post.author.equals(req.user.userId)) {
throw new BadRequestError('You have no permission to do that');
}
await Post.deleteOne(post);
res.status(StatusCodes.NO_CONTENT).json({
post,
});
};
I wish someone could help me solve this problem, it is certainly something simple but I am a beginner and I am trying to understand.
I believe the issue is that you are not fetching the posts after delete is successful.
Try this inside the HomePage component:
...
const [isDeleting, setIsDeleting] = useState(false);
const { loading: loadingDelete, error: deleteError } = deletePost;
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
useEffect(() => {
if (!deleteError && isDeleting && !loadingDelete) {
dispatch(getPosts());
}
setIsDeleting(loadingDelete);
}, [dispatch, deleteError, isDeleting, loadingDelete]);
...
Another method is to use "filtering", but you have to update your reducer as such:
export const deletePostReducer = (state = {}, action) => {
switch (action.type) {
case DELETE_POST_BEGIN:
return { loading: true };
case DELETE_POST_SUCCESS:
return { loading: false, data: action.payload}; // <-- this was changed
case DELETE_POST_FAIL:
return { loading: false, error: action.payload.msg };
default:
return state;
}
};
Now in your HomePage component, you will do something like this when rendering:
...
const { loading: loadingDelete, data: deletedPost } = deletePost;
...
useEffect(() => {
dispatch(getPosts());
if (deletedPost) {
console.log(deletedPost);
}
}, [dispatch, deletedPost]);
return (
...
<Row>
{posts.filter(post => post._id !== deletedPost?._id).map(post => (
<Col lg={4} key={post._id} className='mb-3'>
<Post post={post} />
</Col>
))}
</Row>
)

how to create poll using API with react functional component

this is my react js code and I want to connect with my node js API but I don't understand how to that ...!
import React, { useState } from "react";
import Poll from "react-polls";
// import "./styles.css";
/**
* https://stackoverflow.com/questions/65896319/react-js-class-poll-convert-into-react-hooks-poll
*/
// Declaring poll question and answers
const pollQuestion = "Youtube is the best place to learn ?";
const answers = [
{ option: "Yes", votes: 7 },
{ option: "No", votes: 2 },
{ option: "don't know", votes: 1 },
];
const Fakepolls = () => {
// Setting answers to state to reload the component with each vote
const [pollAnswers, setPollAnswers] = useState([...answers]);
// Handling user vote
// Increments the votes count of answer when the user votes
const handleVote = (voteAnswer) => {
setPollAnswers((pollAnswers) =>
pollAnswers.map((answer) =>
answer.option === voteAnswer
? {
...answer,
votes: answer.votes + 1,
}
: answer
)
);
};
return (
<div>
<Poll
noStorage
question={pollQuestion}
answers={pollAnswers}
onVote={handleVote}
/>
</div>
);
};
export default function App() {
return (
<div className="App">
<Fakepolls />
</div>
);
}
It work's fine with
// Declaring poll question and answers
const pollQuestion = "Youtube is the best place to learn ?";
const answers = [
{ option: "Yes", votes: 7 },
{ option: "No", votes: 2 },
{ option: "don't know", votes: 1 },
];
but I want to connect this poll with my API instead of Declaring it ..! this is my api- to get data -> ( router.get("/poll/:pollId", getPoll); //)
exports.getPoll = async (req, res, next) => {
try {
const { pollId } = req.params;
const polls = await Poll.findById(pollId);
if (!polls) throw new Error("no polls found");
res.status(200).json(polls);
} catch (error) {
error.status = 400;
next(error);
}
};
This is a postman image -
and this API for POST data- and my node js code -
exports.votes = async (req, res, next) => {
try {
/**
* 1. get the poll from db
* 2. check if the user already exists in any option
* 3. if user has already selected any option do nothing
* 4. if user has selected any other option remove from that option
* 5. if user does not exist in any option, insert his user id to selected option
*/
const { pollId } = req.params;
let { userId, answer } = req.body;
// get selected poll from db
const poll = await Poll.findById(pollId);
if (answer && poll) {
answer = answer.toLowerCase();
///Finf the Poll
let existingVote = null;
Object.keys(poll.options).forEach((option) => {
// loop on all options, check if the user already exists in any option
if (poll.options[option].includes(userId)) {
existingVote = option;
}
});
if (existingVote == null) {
// if there is no existing vote save it to db
try {
const push = {};
push[`options.${answer}`] = userId;
const update = await Poll.findByIdAndUpdate(
pollId,
{ $push: push },
{ upsert: true }
);
res.status(201).json(update);
} catch (err) {
error.status = 400;
next(error);
}
} else if (existingVote && existingVote.length > 0) {
// check if answer is same as previous, if yes send not modified
if (existingVote.toLowerCase() === answer.toLowerCase()) {
res.status(304).send("Response already saved");
} else {
// delete the previous response and save it in new
if (
Array.isArray(poll.options[existingVote]) &&
poll.options[existingVote].length > 0
) {
// TODO: filtering this is not returning array but 1
poll.options[existingVote] = poll.options[existingVote].filter(
(vote) => vote != userId
);
poll.options[answer] = poll.options[answer].push(userId);
const update = await Poll.findByIdAndUpdate(pollId, {
$set: { options: poll.options },
});
res.status(201).json(update);
}
}
} else {
error = {
status: 500,
message: "Something went wrong",
};
next(error);
}
} else {
error = {
status: 404,
message: "Poll not found",
};
next(error);
}
} catch (error) {
error.status = 400;
next(error);
}
};
this is a POSTMAN image using POST to store data --- >
how can I connect API with react poll
What you'd do is make a fetch() to your /api/polls endpoint inside your Fakepolls component, the URL being exactly as you show in your Postman screenshot. More info on fetch here at the MDN docs.
With the response you get from the endpoint, populate the answers array you component uses. From what I see, it would require a bit of transformation as your answer object is not quite the same as what Poll needs.
Next, upon user action, as well as updating the votes in the UI, you need to make another fetch to your vote endpoint.
Here's your component again with these adjustments. Keep in mind it's untested and the URLs are obviously not real:
import React, { useState, useEffect } from "react";
import Poll from "react-polls";
// import "./styles.css";
/**
* https://stackoverflow.com/questions/65896319/react-js-class-poll-convert-into-react-hooks-poll
*/
const Fakepolls = () => {
// Setting answers to state to reload the component with each vote
const [pollQuestion, setPollQuestion] = useState('');
const [pollAnswers, setPollAnswers] = useState([]);
// Query the actual poll info from the server
useEffect(() => {
fetch('http://your-server/api/polls/you-poll-id')
.then((response) => response.json()) //parse response as json
.then((pollObject) => {
let answerCountDictionary = Object.keys(pollObject.options)
.map(oKey => {
return {
option: oKey,
anwers: pollObject.options[oKey].length
}
}); //iterate over the 'options' properties' keys to get the names and the current votes
setPollAnswers(answerCountDictionary);
setPollQuestion(pollObject.question)
})
.catch((error) => {
console.error('Error:', error);
});
},[]) //adding empty array of dependencies to prevent multiple calls on state change
// Handling user vote
// Increments the votes count of answer when the user votes
const handleVote = (voteAnswer) => {
setPollAnswers((pollAnswers) =>
pollAnswers.map((answer) =>
answer.option === voteAnswer
? {
...answer,
votes: answer.votes + 1,
}
: answer
)
);
//also submit the backend
fetch('http://your-server/api/vote/poll-id', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: {
"userId": "the-logged-in-user",
"answer": voteAnswer
},
})
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
};
return (
<div>
<Poll
noStorage
question={pollQuestion}
answers={pollAnswers}
onVote={handleVote}
/>
</div>
);
};
export default function App() {
return (
<div className="App">
<Fakepolls />
</div>
);
}

Cannot read property from props in react component

In ChatRoom Component, I am trying to load chat between 2 users to render the names of users of the chat. To get the chat, I am firing off getCurrentChat function.
ChatRoom Component
// importing everything
import { getCurrentChat } from '../../actions/chatActions';
class ChatRoom extends Component {
componentDidMount() {
// loading chat between 2 people
this.props.getCurrentChat(this.props.match.params.chatId);
};
render() {
const { loadingCurrentChat } = this.props.chat;
console.log(this.props.chat.currentChat);
return (
<div className="container">
{loadingCurrentChat ? <Spinner /> : (
<div className="row">
<h3>ChatId: {this.props.chat.currentChat._id}</h3>
<h2>Chat between {this.props.chat.currentChat.user1.name} и {this.props.chat.currentChat.user2.name}</h2>
</div>
)}
</div>
)
}
}
const mapStateToProps = (state) => ({
auth: state.auth,
chat: state.chat
});
export default connect(mapStateToProps, { getCurrentChat })(withRouter(ChatRoom));
chatActions.js
export const getCurrentChat = (chatId) => (dispatch) => {
dispatch(setLoadingCurrentChat());
axios.get(`/chat/${chatId}`)
.then(res =>
dispatch({
type: GET_CURRENT_CHAT,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err
})
);
};
chatReducer.js
// importing everything
const initialState = {
currentChat: {},
loadingCurrentChat: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case SET_LOADING_CURRENT_CHAT:
return {
...state,
loadingCurrentChat: true
}
case GET_CURRENT_CHAT:
return {
...state,
currentChat: action.payload,
loadingCurrentChat: false
}
}
}
server file where I handle requests from chatActions.js -
chatController.js
// requiring everything
exports.getCurrentChat = (req, res) => {
const chatId = req.params.chatId;
Chat.findById(chatId)
.populate('user1')
.populate('user2')
.exec()
.then(chat => res.json(chat))
.catch(err => res.status(400).json(err));
};
When I try console.log the currentChat in ChatRoom, it correctly shows the chat.
currentChat:
messages: []
user1: {
_id: "5d1328a91e0e5320706cdabb",
name: "sarvar",
}
user2: {
_id: "5d131405ce36ce0ebcf76ae1",
name: "jalol makhmudov"
}
__v: 0
_id: "5d329aea3f34fe0b8c6cf336"
If I render currentChat._id (see <h3> element in ChatRoom) it correctly displays it.
But if I render currentChat.user1.name and currentChat.user2.name (see <h2> element in ChatRoom) it gives an error
TypeError: Cannot read property 'name' of undefined
Solution
Initialize state with a more accurate shape.
const initialState = {
currentChat: {
user1: {}
},
loadingCurrentChat: false,
};
If you cannot do that, put a check like currentChat.user1 && currentChat.user1.name before accessing it in JSX.
Explanation
getCurrentChat is a request which means it will take time to fetch the data. React does not wait for the request to be completed for rendering. One of the reasons why we define initialState is because while the request is being completed, React uses initialState to render.
In your case, initialState is defined as,
const initialState = {
currentChat: {},
loadingCurrentChat: false,
};
In JavaScript, when you define an empty object currentChat: {}, you can access its immediate child without any error. Therefore currentChat._id is accessible but since currentChat.user1 is undefined, currentChat.user1.name will throw an error.

Resources