i have a simple app, that fetch my database via graphql node.js server, and i want to implement subscriptions to it, backend is already written, but i don`t know what to do in frontend.
so i have a query and subscription, and i`m returning a map of data with messages and displaying it in my application, what i need to update, to make it work with subscriptions?
export const SUBSCRIPTION_MESSAGES = gql`
subscription {
messageCreated {
content
id
user
}
}
`;
export const GET_MESSAGES = gql`
query {
messages {
content
id
user
}
}
`;
<Messages user={state.user} />
--------------------
import { useQuery, useSubscription } from "#apollo/client";
import { useEffect } from "react";
import { GET_MESSAGES, SUBSCRIPTION_MESSAGES } from "../graphql/queries";
const Messages = ({ user }: any) => {
const { subscribeToMore, data } = useQuery(GET_MESSAGES);
console.log(subscribeToMore);
useEffect(() => {
subscribeToMore({
document: SUBSCRIPTION_MESSAGES,
updateQuery: (prev, { subscriptionData }: any) => {
if (!subscriptionData.data) return prev;
const newMessage = subscriptionData.data.messageCreated;
return Object.assign({}, prev, {
messages: [newMessage, ...prev.messages],
});
},
});
}, []);
if (!data) {
console.log("lol");
return null;
}
return (
<div>
{data.messages.map(({ id, user: messageUser, content }: any) => (
<div
key={id}
style={{
display: "flex",
justifyContent: user == messageUser ? "flex-end" : "flex-start",
paddingBottom: "1em",
}}
>
{user != messageUser && (
<div
style={{
height: 50,
width: 50,
marginRight: "0.5em",
border: "2px solid #e5e6ea",
borderRadius: 25,
textAlign: "center",
fontSize: "18pt",
paddingTop: 5,
}}
>
{messageUser.slice(0, 2).toUpperCase()}
</div>
)}
<div
style={{
background: user == messageUser ? "#58bf56" : "#e5e6ea",
color: user == messageUser ? "white" : "black",
paddingBottom: "1em",
borderRadius: "1em",
maxWidth: "60%",
padding: "10px",
}}
>
{content}
</div>
</div>
))}
</div>
);
};
export default Messages;
Related
I have created a survey form with formik in react hooks. Initial state values of react variables are fetched by the database. I have created an icon button that increments the value of votes by one onclick. The problem is whenever I have clicked on icon value not incremented by.
Initial state before clicking on icon
result after clicking on icon
I have attached the above screenshot of the output before and after a click on the button.
Code:
// #ts-ignore
import React, { useState, useEffect } from 'react';
import { Formik, Form, Field } from 'formik';
import {
Typography,
Button,
Grid,
CircularProgress,
Divider,
} from '#material-ui/core';
import * as Yup from 'yup';
import { MyInput } from './comman/MyInput';
import axios from 'axios';
import { Event } from '../Tracking/Event';
import ThumbUpAltIcon from '#material-ui/icons/ThumbUpAlt';
import Unsubscribed from './Unsubscribed';
const contactSchema = Yup.object().shape({});
export default function SurveyUnsubscribed(props: any) {
const [isSubmitted, setIsSubmitted] = useState(false);
const [loading, setLoading] = useState(false);
const [count, setCount] = useState(0);
const [countone, setCountOne] = useState(0);
const [counttwo, setCountTwo] = useState(0);
const [countthree, setCountThree] = useState(0);
const [state, setState] = useState({
msg: '',
});
async function getInitialValues() {
try {
const response = await axios.get(
`${process.env.REACT_APP_LOCALHOST_DEVELOPMENT_VOTING_API_GET}`,
);
var voteone = response.data.map(function (obj: any) {
console.log('value',parseInt(obj.voteone))
return parseInt(obj.voteone);
});
var votetwo = response.data.map(function (obj: any) {
return obj.votetwo;
});
var votethree = response.data.map(function (obj: any) {
return obj.votethree;
});
var votefour = response.data.map(function (obj: any) {
return obj.votefour;
});
return {
voteone,
votetwo,
votethree,
votefour,
helptext: '',
};
} catch (error) {
console.error(error);
}
}
useEffect(() => {
getInitialValues().then((res: any) => setCount(res.voteone));
getInitialValues().then((res: any) => setCountOne(res.votetwo));
getInitialValues().then((res: any) => setCountTwo(res.votethree));
getInitialValues().then((res: any) => setCountThree(res.votefour));
}, []);
return (
<React.Fragment>
<Formik
enableReinitialize
initialValues={{
count: count,
countone: countone,
counttwo: counttwo,
countthree: countthree,
helptext: '',
}}
validationSchema={contactSchema}
onSubmit={(values, { resetForm }) => {
setLoading(true);
const data = {
count: values.count,
countone: values.countone,
counttwo: values.counttwo,
countthree: values.countthree,
helptext: values.helptext,
};
const request = new Request(
`${process.env.REACT_APP_LOCALHOST_DEVELOPMENT_VOTING_API}`,
{
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json',
}),
body: JSON.stringify(data),
},
);
fetch(request)
.then((res) => res.json())
.then((data) => {
if (data.message === 'Thank you for your feedback!') {
setState({
msg: data.message,
});
setIsSubmitted(true);
setTimeout(() => {
setLoading(false);
}, 1500);
} else {
console.log('error');
}
});
setTimeout(() => {
setIsSubmitted(false);
}, 1500);
resetForm();
}}
>
{({ setFieldValue }) => (
<Grid container>
<Grid
item
xs={12}
style={{ paddingLeft: '2em', paddingRight: '2em' }}
>
{isSubmitted}
<Form>
<Typography
variant="body1"
style={{
display: 'flex',
alignItems: 'center',
marginBottom: '1em',
}}
>
<Field
type="hidden"
name="count"
component={ThumbUpAltIcon}
onClick={() => setCount( count + 1 )}
style={{ color: '#C4C4C4', marginRight: '0.4em' }}
/>
<Typography
variant="caption"
style={{
position: 'relative',
top: '1.5em',
right: '1.5em',
}}
>
{count}
</Typography>
Complaince and Tax Return Filing Dates.
</Typography>
<Typography
variant="body1"
style={{
display: 'flex',
alignItems: 'center',
marginBottom: '1em',
}}
>
<Field
type="hidden"
name="countone"
component={ThumbUpAltIcon}
onClick={() => setCountOne( countone + 1 )}
style={{ color: '#C4C4C4', marginRight: '0.4em' }}
/>
<Typography
variant="caption"
style={{
position: 'relative',
top: '1.5em',
right: '1.5em',
}}
>
{countone}
</Typography>
Excel Sheet for Calculation of GSTR 3B.
</Typography>
<Typography
variant="body1"
style={{
display: 'flex',
alignItems: 'center',
marginBottom: '1em',
}}
>
<Field
type="hidden"
name="counttwo"
component={ThumbUpAltIcon}
onClick={() => setCountTwo( counttwo + 1 )}
style={{ color: '#C4C4C4', marginRight: '0.4em' }}
/>
<Typography
variant="caption"
style={{
position: 'relative',
top: '1.5em',
right: '1.5em',
}}
>
{counttwo}
</Typography>
Excel Sheet for GSTR 2A vs ITC Books matching.
</Typography>
<Typography
variant="body1"
style={{
display: 'flex',
alignItems: 'center',
marginBottom: '1em',
}}
>
<Field
type="hidden"
name="countthree"
component={ThumbUpAltIcon}
onClick={() => setCountThree(countthree + 1)}
style={{ color: '#C4C4C4', marginRight: '0.4em' }}
/>
<Typography
variant="caption"
style={{
position: 'relative',
top: '1.5em',
right: '1.5em',
}}
>
{countthree}
</Typography>
Details of applicable law on directors and requirement of
Director KYC.
</Typography>
<br />
<Typography
variant="h6"
style={{
marginLeft: '2em',
marginTop: '0.5em',
}}
>
What information would help you ?
</Typography>
<Field
id="outlined-multiline-flexible"
type="text"
name="helptext"
component={MyInput}
disabled={isSubmitted}
style={{ marginLeft: '2em' }}
/>
<br />
<br />
<Button
type="submit"
variant="contained"
style={{
background: '#2F4858',
color: 'white',
fontFamily: 'roboto',
fontSize: '1rem',
marginLeft: '2em',
marginBottom: '1em',
}}
>
{loading && (
<CircularProgress
size={25}
color="inherit"
style={{ marginRight: '5px' }}
/>
)}
{loading && <span>submitting</span>}
{!loading && <span>Submit</span>}
</Button>
<br />
{state.msg && (
<Typography
variant="h6"
style={{
color: '#4BB543',
fontFamily: 'roboto-medium',
marginTop: '1em',
}}
>
{' '}
{state.msg}{' '}
</Typography>
)}
</Form>
<Divider
style={{
border: '1px solid #97A1A8',
marginTop: '1em',
marginBottom: '2em',
}}
/>
</Grid>
</Grid>
)}
</Formik>
</React.Fragment>
);
}
Whenever I have clicked on the thumbs-up icon value is incremented by one but value 1 is inserted in front of the previous one. I want to increment value by one. ( 1 increment by 2 ).
Thanks In advance guys.
It's the nature of javascript when you add a string to a number
console.log(1+"1") // <-- Gives "11"
console.log(1+1) // <-- Gives "2"
Your first count is fine, but when you get a response from the API, your res.voteone is definitely a string.
So you will need to parse your response to a number first:
useEffect(() => {
getInitialValues().then((res: any) => setCount(Number(res.voteone)));
getInitialValues().then((res: any) => setCountOne(Number(res.votetwo)));
getInitialValues().then((res: any) => setCountTwo(Number(res.votethree)));
getInitialValues().then((res: any) => setCountThree(Number(es.votefour)));
}, []);
OR
useEffect(() => {
getInitialValues().then((res: any) => setCount(parseInt(res.voteone)));
getInitialValues().then((res: any) => setCountOne(parseInt(res.votetwo)));
getInitialValues().then((res: any) => setCountTwo(parseInt(res.votethree)));
getInitialValues().then((res: any) => setCountThree(parseInt(es.votefour)));
}, []);
Number() and parseInt() behaves a bit different but in your case it's not.
This is the result of type coercion. As in, in javasript "1"+1 gives 11. To fix, you should convert the results from response to number.
useEffect(() => {
getInitialValues().then((res: any) => setCount(Number(res.voteone)));
getInitialValues().then((res: any) => setCountOne(Number(res.votetwo)));
getInitialValues().then((res: any) => setCountTwo(Number(res.votethree)));
getInitialValues().then((res: any) => setCountThree(Number(res.votefour)));
}, []);
They have passed me a project of a web shop made with node.js and reactj and when I execute it loads the web but then it leaves an error message, it is because in some section there is no data or there is no code, but I do not want to insert it, my Professor told me to put a conditional so that when I do not find code I also upload the web but I do not know where to put the code or where to act, i am new on stackoverflow, thanks a lot
60 | let campaigns: any = [];
61 |
62 | if (this.props.categories && this.props.categories.isFinished) {
> 63 | if (this.props.categories.queryResult.data) {
| ^
64 | categories = this.props.categories.queryResult.data;
65 | } else if (this.props.categories.queryResult.length) {
66 | categories = this.props.categories.queryResult;
import * as React from "react";
import { Component } from "react";
import "./MenuBar.css";
import Menu from "./Menu";
import { List } from "semantic-ui-react";
import icon1 from "../../assets/icons/bars1.png";
import icon2 from "../../assets/icons/bars2.png";
import icon3 from "../../assets/icons/bars3.png";
import icon1r from "../../assets/icons/bars1_w.png";
import icon2r from "../../assets/icons/bars2_w.png";
import icon3r from "../../assets/icons/bars3_w.png";
import { services } from "../../store";
import { connect } from "react-redux";
import Radium from "radium";
import MenuFilters from "./MenuFilters";
export interface Props {
// Actions
fetchCategories: any;
fetchShops: any;
fetchCampaigns: any;
// Props
name: string;
avatar: string;
userId: string;
classes: any;
categories: any;
shops: any;
campaigns: any;
// Events
onChangeGrid: any;
}
interface State {
isOpen: boolean;
grid: string;
}
class _MenuBar extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
isOpen: true,
grid: "grid1"
};
}
public componentDidMount() {
this.props.fetchCategories();
this.props.fetchShops();
this.props.fetchCampaigns();
}
public render() {
let categories: any = [];
let shops: any = [];
let campaigns: any = [];
if (this.props.categories && this.props.categories.isFinished) {
if (this.props.categories.queryResult.data) {
categories = this.props.categories.queryResult.data;
} else if (this.props.categories.queryResult.length) {
categories = this.props.categories.queryResult;
}
}
if (this.props.shops && this.props.shops.isFinished) {
if (this.props.shops.queryResult.data) {
shops = this.props.shops.queryResult.data;
} else if (this.props.shops.queryResult.length) {
shops = this.props.shops.queryResult;
}
}
if (this.props.campaigns && this.props.campaigns.isFinished) {
if (this.props.campaigns.queryResult.data) {
campaigns = this.props.campaigns.queryResult.data;
} else if (this.props.campaigns.queryResult.length) {
campaigns = this.props.campaigns.queryResult;
}
}
return (
<div className="MCMenuBar">
<div className="MCMenuBarContainer">
<div
style={{
display: "inline-flex",
width: "50%"
}}
>
<Menu categories={categories} shops={shops} campaigns={campaigns} />
<div className="MCMenuBarDivider" />
<div
style={{
height: "50px",
marginTop: "10px"
}}
>
<List horizontal>
<List.Item as="a">
<span style={{ color: "#000", fontWeight: "bold" }}>
NUEVOS
</span>
</List.Item>
<List.Item as="a">
<span style={{ color: "#000", fontWeight: "bold" }}>
GRATIS
</span>
</List.Item>
<List.Item as="a">
<span style={{ color: "#000", fontWeight: "bold" }}>
PROMOS
</span>
</List.Item>
<List.Item as="a">
<span style={{ color: "#000", fontWeight: "bold" }}>
JUEGOS
</span>
</List.Item>
</List>
</div>
</div>
<div
style={{
height: "38px",
width: "50%",
textAlign: "right"
}}
>
<List horizontal>
<List.Item
as="a"
onClick={() => {
if (this.props.onChangeGrid) {
this.setState({ grid: "grid1" });
this.props.onChangeGrid("grid1");
}
}}
>
<span style={{ color: "#000", fontWeight: "bold" }}>
<img
src={this.state.grid === "grid1" ? icon1 : icon1r}
alt="Mi chollo"
style={style.baricon}
/>
</span>
</List.Item>
<List.Item
as="a"
onClick={() => {
if (this.props.onChangeGrid) {
this.setState({ grid: "grid2" });
this.props.onChangeGrid("grid2");
}
}}
>
<span style={{ color: "#000", fontWeight: "bold" }}>
<img
src={this.state.grid === "grid2" ? icon2 : icon2r}
alt="Mi chollo"
style={style.baricon}
/>
</span>
</List.Item>
<List.Item
as="a"
onClick={() => {
if (this.props.onChangeGrid) {
this.setState({ grid: "grid3" });
this.props.onChangeGrid("grid3");
}
}}
>
<span style={{ color: "#000", fontWeight: "bold" }}>
<img
src={this.state.grid === "grid3" ? icon3 : icon3r}
alt="Mi chollo"
style={style.baricon}
/>
</span>
</List.Item>
<List.Item>
<div />
</List.Item>
</List>
<div
style={{
display: "inline-flex",
borderLeft: "1px solid #ededed",
paddingLeft: "10px",
height: "58px",
marginTop: "-12px",
position: "relative",
top: "2px"
}}
>
<div
style={{
display: "inline-flex",
paddingTop: "10px"
}}
>
<MenuFilters />
</div>
</div>
</div>
</div>
</div>
);
}
}
const style = {
baricon: {
width: "24px",
height: "24px",
opacity: 0.4
}
};
const mapDispatchToProps = (dispatch: any) => {
return {
// same effect
fetchCategories: () => {
dispatch(services["api/v1/categories"].find());
},
fetchShops: () => {
dispatch(services["api/v1/shops"].find());
},
fetchCampaigns: () => {
dispatch(services["api/v1/campaigns"].find());
}
};
};
const mapStateToProps = (store: any) => {
return {
categories: store.categories,
shops: store.shops,
campaigns: store.campaigns
};
};
const MenuBar = connect(
mapStateToProps,
mapDispatchToProps
)(_MenuBar);
export default Radium(MenuBar);
As the error explains your queryResult property is null. Add another conditional check to see if the queryResult field is not empty in the line above (62)
if (
this.props.categories &&
this.props.categories.isFinished &&
!!this.props.categores.queryResult
) {
// user queryResult
}
Another option would be to set a default value for queryResult and update all it's references.
const qResult = this.props.categories.queryResult || [];
if (response && response.data && response.data.length > 0) {
}
I have 2 React components, MyPolls and NewPoll.
MyPolls renders the last 4 polls created by a certain user and each time you press a 'Load more button', it renders 4 more. This is done by making a mongoose call to my mongoDB by sorting in desending order by date and using skip and limit.
My problem right now is that whenever I create a new poll, it redirects me to the MyPolls component via history.push('/mypolls') in my action creator and the order of my polls list gets messed up.
Let's say there are currently 8 polls in my mongoDB:
1 2 3 4 5 6 7 8 (1 being oldest, and 8 being newest).
When I view MyPolls, it will show 8 7 6 5. And if you click 'Load More', you will see the other 4: 8 7 6 5 4 3 2 1.
But after you create a new poll, 9, you will get redirected to MyPolls and it will show this order 8 7 6 5 9 8 7 6 instead (shows 8 instead of 4 on initial rendering).
What's causing this? It seems like my reducer state is not resetting ?
MyPolls.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Loading from '../Loading';
import Poll from './Poll';
class MyPolls extends Component {
constructor(props) {
super(props);
this.state = {
skip: 0,
isLoading: true,
isLoadingMore: false,
};
}
componentDidMount() {
this.props.fetchMyPolls(this.state.skip)
.then(() => {
setTimeout(() => {
this.setState({
skip: this.state.skip + 4,
isLoading: false
});
}, 1000);
});
}
loadMore(skip) {
this.setState({ isLoadingMore: true });
setTimeout(() => {
this.props.fetchMyPolls(skip)
.then(() => {
const nextSkip = this.state.skip + 4;
this.setState({
skip: nextSkip,
isLoadingMore: false
});
});
}, 1000);
}
renderPolls() {
return this.props.polls.map(poll => {
return (
<Poll
key={poll._id}
title={poll.title}
options={poll.options}
/>
)
})
}
render() {
console.log(this.props.polls);
console.log('skip:', this.state.skip);
return (
<div className='center-align container'>
<h2>My Polls</h2>
{this.state.isLoading ? <Loading size='big' /> :
<div
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-evenly',
alignItems: 'center',
alignContent: 'center'
}}>
{this.renderPolls()}
</div>}
<div className='row'>
{this.state.isLoadingMore ? <Loading size='small' /> :
<button
className='btn red lighten-2 wave-effect waves-light' onClick={() => this.loadMore(this.state.skip)}>
Load More
</button>}
</div>
</div>
);
}
}
function mapStateToProps({ polls }) {
return { polls }
}
export default connect(mapStateToProps, actions)(MyPolls);
NewPoll.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm, Field, FieldArray, arrayPush } from 'redux-form';
import * as actions from '../../actions';
import { withRouter } from 'react-router-dom';
const cardStyle = {
width: '500px',
height: '75px',
margin: '10px auto',
display: 'flex',
alignItems: 'center',
padding: '10px'
};
class NewPoll extends Component {
constructor(props) {
super(props);
this.state = {
showOptions: false,
option: '',
title: ''
};
this.onOptionInputChange = this.onOptionInputChange.bind(this);
this.onAddOption = this.onAddOption.bind(this);
this.renderOption = this.renderOption.bind(this);
this.renderOptionCard = this.renderOptionCard.bind(this);
this.renderTitle = this.renderTitle.bind(this);
this.renderTitleCard = this.renderTitleCard.bind(this);
}
onOptionInputChange(event) {
this.setState({ option: event.target.value });
}
onAddOption() {
const { dispatch } = this.props;
dispatch(arrayPush('newPollForm', 'options', this.state.option));
this.setState({ option: '' });
}
renderOption(props) {
return (
<ul>
{props.fields.map((option, index) => (
<li key={index}>
<div
className='card'
style={cardStyle}>
<Field
type='text'
name={option}
index={index}
component={this.renderOptionCard}
/>
<i
className='material-icons right'
onClick={() => props.fields.remove(index)}
>
delete
</i>
</div>
<div className='red-text'>
{props.meta.error }
</div>
</li>
))}
</ul>
);
}
renderOptionCard({ index, input }) {
return (
<span className='card-title'
style={{ flex: '1' }}>
{`${index + 1})`} {input.value}
</span>
);
}
renderTitle({ input, type, placeholder, meta: { touched, error }}) {
return (
<div>
<div className='input-field inline'>
<input {...input}
type={type}
placeholder={placeholder}
style={{ width: '350px' }}
/>
<div className='red-text'>
{touched && error}
</div>
</div>
<button
type='text'
className='red lighten-2 btn waves-effect waves-light'
onClick={() => {
this.setState({ title: input.value });
input.value = '';
}}
disabled={!input.value}>
Add Title
<i className='material-icons right'>
add
</i>
</button>
</div>
)
}
renderTitleCard({ input }) {
return (
<div
className='card'
style={cardStyle}>
<span className='card-title' style={{ flex: '1' }}>
<strong><u>{input.value}</u></strong>
</span>
<i className='material-icons right' onClick={() => this.setState({ title: '' })}>
delete
</i>
</div>
)
}
onPollSubmit(values) {
const { history } = this.props;
this.props.submitPoll(values, history);
}
render() {
return (
<div className='center-align'>
<h3>Create a new poll:</h3>
<form onSubmit={this.props.handleSubmit(this.onPollSubmit.bind(this))}>
<Field
type='text'
placeholder='Title'
name='title'
component={this.state.title ? this.renderTitleCard : this.renderTitle}
/>
<FieldArray
name='options' component={this.renderOption}
/>
<div className='row'>
<div className='inline input-field'>
<input
value={this.state.option} onChange={this.onOptionInputChange}
placeholder='Option'
style={{ width: '300px' }}
/>
</div>
<button
type='text'
className='red lighten-2 btn waves-effect waves-light'
onClick={this.onAddOption}
disabled={!this.state.option}
>
Add Option
<i className='material-icons right'>
add
</i>
</button>
</div>
<button
type='submit'
className='teal btn-large waves-effect waves-light'
>
Submit
<i className='material-icons right'>
send
</i>
</button>
</form>
</div>
);
}
}
function validate(values) {
const errors = {};
if (!values.title) {
errors.title = 'You must provide a title';
}
if (!values.options || values.options.length < 2) {
errors.options = { _error: 'You must provide at least 2 options' };
}
return errors;
}
NewPoll = reduxForm({
form: 'newPollForm',
validate
})(NewPoll);
export default connect(null, actions)(withRouter(NewPoll));
action creators:
export const submitPoll = (values, history) => async dispatch => {
const res = await axios.post('/api/polls', values);
history.push('/mypolls');
dispatch({ type: FETCH_USER, payload: res.data });
}
export const fetchMyPolls = (skip) => async dispatch => {
const res = await axios.get(`/api/mypolls/${skip}`);
dispatch({ type: FETCH_MY_POLLS, payload: res.data });
}
Poll routes:
app.post('/api/polls', requireLogin, (req, res) => {
const { title, options } = req.body;
const poll = new Poll({
title,
options: options.map(option => ({ option: option.trim() })),
dateCreated: Date.now(),
_user: req.user.id
});
poll.save();
res.send(req.user);
});
app.get('/api/mypolls/:skip', requireLogin, (req, res) => {
Poll.find({ _user: req.user.id })
.sort({ dateCreated: -1 })
.skip(parseInt(req.params.skip))
.limit(4)
.then(polls => {
res.send(polls);
});
});
Poll reducer:
import { FETCH_MY_POLLS, UPDATE_POLL } from '../actions/types';
export default function(state = [], action) {
switch(action.type) {
case FETCH_MY_POLLS:
return [ ...state, ...action.payload];
case UPDATE_POLL:
return (
[...state].map(poll => {
if (poll._id === action.payload._id) {
return action.payload;
}
return poll;
})
)
default:
return state;
}
}
Demo of the app: https://voting-app-drhectapus.herokuapp.com
(Use riverfish#gmail.com and password 123 to login)
Github: https://github.com/drhectapus/voting-app
The MyPolls component is calling fetchMyPolls with 0 on componentDidMount. What is happening is, at first you are visiting /mypolls and server returns polls [8, 7, 6, 5]. This make your polls state [8,7,6,5]. When you create a new poll (say 9), you are redirected to /mypolls/ and calling againg fetchMyPolls with 0. Pay attention that in pollsReducer you have
case FETCH_MY_POLLS:
return [ ...state, ...action.payload];
which simply appends new polls to the end of the state. That's why the new state becomes [8, 7, 6, 5, 9, 8, 7, 6].
You are right that reducer doesn't reset the state. There is no action described in your app doing this. Indeed, it is a good thing not to reset your state, because the client already received the information, why not to use them instead of making a new request to the backend.
A good solution would be defining a new action, eg. FETCH_NEW_POLL and adding a new case to the pollsReducer
case FETCH_NEW_POLL
return [...action.payload, ...state];
You would also need to modify your reducer to have only unique items in your state.
In addition, in App.js you can use onEnter to fetch first 4 polls only if there is none in the state. This way you can remove fetchMyPolls call from the componentDidMount of MyPolls.
I need to convert this from a functional component to a class component so I can take advantage of the componentDidMount method of React.Component.
const receivedStyle = {
marginRight: '0',
marginLeft: 'auto',
};
const receivedBubble = {
backgroundColor: '#709AFF',
color: 'white',
};
const receivedDate = {
marginRight: '0',
marginLeft: 'auto',
};
const MessageBubble = ({ message, received }) => (
<div className="message-bubble" style={received ? receivedStyle : null}>
<div className="bubble" style={received ? receivedBubble: null}>
{message.message}
</div>
<span className="date" style={received ? receivedDate: null}>{Moment(message.timestamp).startOf('minute').fromNow()}</span>
</div>
);
export default MessageBubble;
I don't understand what's the problem. Anyway, here it goes:
import React, { Component } from 'react'
const receivedStyle = {
marginRight: '0',
marginLeft: 'auto',
}
const receivedBubble = {
backgroundColor: '#709AFF',
color: 'white',
}
const receivedDate = {
marginRight: '0',
marginLeft: 'auto',
}
export default class MessageBubble extends Component {
componentDidMount() {
...
}
render() {
const { message, received } = this.props
return (
<div className="message-bubble" style={received ? receivedStyle : null}>
<div className="bubble" style={received ? receivedBubble: null}>
{message.message}
</div>
<span
className="date"
style={received ? receivedDate: null}
>
{Moment(message.timestamp).startOf('minute').fromNow()}
</span>
</div>
)
}
}
I have a react native component that has username and password text input fields. I want the values which are entered to be available in the node js controller to query my db. I am not able to use AsyncStorage as I cannot import AsyncStorage inside controller. How do I make this work? Please help.
My login screen:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Image,
TextInput,
Alert,
Navigator,
TouchableHighlight,
BackAndroid,
ScrollView,
AsyncStorage,
} from 'react-native';
var _navigator;
import Container from './Container';
import Button from './Button';
import Label from './Label';
import ImageContainer from './ImageContainer';
import RegisterView from './RegisterView';
export default class LoginView extends Component {
constructor(props) {
super(props);
this.state = {
userName: '',
userPass: '',
error:null,
};
}
_navigate(){
this.props.navigator.push({
name: 'RegisterView', // Matches route.name
})
}
_handleAdd = () => {
if((this.state.userName)!=="" && (this.state.userPass)!=="" ){
const data = {
username: this.state.userName,
password: this.state.userPass
}
// Serialize and post the data
const json = JSON.stringify(data)
fetch('http://10.0.2.2:3000/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: json
})
.then((response) => response.json())
.then((res) => {
if (res.error) {
alert(res.error)
} else {
this.props.navigator.push({
name: 'CheckList', // Matches route.name
})
}
})
.catch(() => {
alert('There was an error logging in.');
})
.done()
}
else{
alert('Cannot be empty!');
}
}
render() {
return (
<ScrollView style={styles.scroll}>
<ImageContainer>
<Image
style={{width:110,height: 110, justifyContent: 'center',
alignItems: 'center'}}
source={require('./Logo.png')}>
</Image>
</ImageContainer>
<Container>
<TextInput
placeholder="Username"
style={styles.textInput}
onChangeText={(text) => this.setState({userName:text})}
autoCapitalize="none"
autoCorrect={false}
onSubmitEditing={(event) => {
const{userName}=this.state.userName;
const{onSubmitEditing}=this.props;
if(!userName) return
onSubmitEditing(userName)
this.refs.SecondInput.focus();
}}
/>
<TextInput
placeholder="Password"
ref='SecondInput'
secureTextEntry={true}
onChangeText={(text) => this.setState({userPass:text})}
style={styles.textInputPass}
/>
</Container>
<Container>
<Button
label="Sign In"
styles={{button: styles.primaryButton, label:
styles.buttonWhiteText}}
onPress={() => this._handleAdd()}
/>
</Container>
<Container>
<TouchableHighlight onPress={ () => this._navigate()}
underlayColor= 'transparent'>
<Text style ={styles.buttonBlackText}>New? Register
</Text>
</TouchableHighlight>
</Container>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
scroll: {
backgroundColor: '#FFFFFF',
padding: 30,
flexDirection: 'column'
},
buttonWhiteText: {
fontSize: 20,
color: '#FFF'
},
buttonBlackText: {
fontSize: 20,
marginTop: 20,
textAlign:'center',
color: '#000000'
},
textInput: {
fontSize: 20,
backgroundColor: '#FFF',
marginBottom: 20,
marginTop: 20
},
textInputPass: {
fontSize: 20,
backgroundColor: '#FFF',
marginBottom: 20,
},
primaryButton: {
backgroundColor: '#34A853'
},
});
Controller to query the username from db. For now, it's hardcoded.
import Profile from '../models/profile';
import moment from 'moment';
export const index = (req, res, next) => {
Profile.find({'username': 'John'}).lean().exec((err, profiles) =>
res.json(
// Iterate through each movie
{ profiles: profiles.map(profile => ({
...profile,
}))}
));
};