I can not set state in this code. I understand it because I did not bind the function getvalue. Otherwise I cannot call this.radius. I this code this.radius is 5000. If I changed it to
getvalues = (e)=> { console.log(e)}
it returns undefined.
So what do I need to do
class Mapspage extends React.Component {
constructor(props) {
super(props);
this.state = {
location: { lat: 52.2387683, lng: 5.8322737 },
position: [],
radius: 5000,
};
}
getvalues(e) {
this.setState({'radius': this.radius});
}
render() {
const MapWithAMarker = withGoogleMap(props => (
<GoogleMap
defaultZoom={11}
defaultCenter={this.state.location}
>
<Circle
radius={this.state.radius}
editable={true}
onRadiusChanged={this.getvalues}
onDragEnd={this.getvalues}
draggable={true}
visible={true}
center={this.state.location}
/>
</GoogleMap>
));
return (
<div className="box box-default">
<MapWithAMarker
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
}
I found the answer. You need to ref the circle component and then you can call the getValues function. Also you need tot return false the shouldComponentUpdate
This is the code
class Mapspage extends React.Component {
constructor(props) {
super(props);
this.state = {
location: { lat: 52.2387683, lng: 5.8322737 },
position: [],
radius: 5000
};
}
mapMounted = ref => {
this.map = ref;
};
shouldComponentUpdate(nextProps, nextState) {
return false;
}
getvalues = e => {
const radius = this.map.getRadius();
const center = this.map.getCenter();
this.setState({ radius: radius });
this.setState({
"location.lat": center.lat(),
"location.lng": center.lng()
});
};
render() {
const MapWithAMarker = withGoogleMap(props => (
<GoogleMap defaultZoom={11} defaultCenter={this.state.location}>
<Circle
ref={this.mapMounted.bind(this)}
radius={this.state.radius}
editable={true}
onRadiusChanged={props.getval}
onDragEnd={props.getval}
draggable={true}
visible={true}
center={this.state.location}
/>
</GoogleMap>
));
return (
<div className="box box-default">
<MapWithAMarker
getval={this.getvalues.bind(this)}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
}
Please try this, the issue seems to be with binding.
class Mapspage extends React.Component {
constructor(props) {
super(props);
this.state = {
location: { lat: 52.2387683, lng: 5.8322737 },
position: [],
radius: 5000,
};
}
getvalues = (e) => {
this.setState({radius: this.radius});
}
render() {
const MapWithAMarker = withGoogleMap(props => (
<GoogleMap
defaultZoom={11}
defaultCenter={this.state.location}
>
<Circle
radius={this.state.radius}
editable={true}
onRadiusChanged={this.getvalues}
onDragEnd={this.getvalues}
draggable={true}
visible={true}
center={this.state.location}
/>
</GoogleMap>
));
return (
<div className="box box-default">
<MapWithAMarker
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
}
Related
I am tring to create a video streaming app, and I got an error logging: "Invalid hook call" when i set "{ ready, tracks } = useMicrophoneAndCameraTracks();" and client = useClient();
I tried not calling the hook useMicrophoneAndCameraTracks and useClient, the error wasn't there but the video won't stream. Here is my code
Settings.js
import { createClient, createMicrophoneAndCameraTracks } from "agora-rtc-react";
const appId = "d13f5075f00d417092285bc8a52f17f2";
const token = "007eJxTYOjZ/X7lClbPsGXOv7vW/egUYDx+N+WSl9Bzddlf2fyLD5orMKQYGqeZGpibphkYpJgYmhtYGhlZmCYlWySaGqUZmqcZKT+bk9wQyMiwSdaKkZEBAkF8FobcxMw8BgYAbJIfFw=="
export const config = { mode: "rtc", codec: "vp8", appId: appId, token: token };
export const useClient = createClient(config);
export const useMicrophoneAndCameraTracks = createMicrophoneAndCameraTracks();
export const channelName = "main";
VideoCall.js
import { useState, useEffect } from "react";
import {
config,
useClient,
useMicrophoneAndCameraTracks,
channelName,
} from "./settings.js";
import { Grid } from "#material-ui/core";
import Video from "./Video";
import Controls from "./Controls";
export default function VideoCall(props) {
const { setInCall } = props;
const [users, setUsers] = useState([]);
const [start, setStart] = useState(false);
const client = useClient();
const { ready, tracks } = useMicrophoneAndCameraTracks();
useEffect(() => {
let init = async (name) => {
client.on("user-published", async (user, mediaType) => {
await client.subscribe(user, mediaType);
if (mediaType === "video") {
setUsers((prevUsers) => {
return [...prevUsers, user];
});
}
if (mediaType === "audio") {
user.audioTrack.play();
}
});
client.on("user-unpublished", (user, mediaType) => {
if (mediaType === "audio") {
if (user.audioTrack) user.audioTrack.stop();
}
if (mediaType === "video") {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
}
});
client.on("user-left", (user) => {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
});
try {
await client.join(config.appId, name, config.token, null);
} catch (error) {
console.log("error");
}
if (tracks) await client.publish([tracks[0], tracks[1]]);
setStart(true);
};
if (ready && tracks) {
try {
init(channelName);
} catch (error) {
console.log(error);
}
}
}, [channelName, client, ready, tracks]);
return (
<Grid container direction="column" style={{ height: "100%" }}>
<Grid item style={{ height: "5%" }}>
{ready && tracks && (
<Controls tracks={tracks} setStart={setStart} setInCall={setInCall} />
)}
</Grid>
<Grid item style={{ height: "95%" }}>
{start && tracks && <Video tracks={tracks} users={users} />}
</Grid>
</Grid>
);
}
App.js
import { useState } from "react";
import { Button } from "#material-ui/core";
import VideoCall from "./VideoCall";
function App() {
const [inCall, setInCall] = useState(false);
return (
<div className="App" style={{ height: "100%" }}>
{inCall ? (
<VideoCall setInCall={setInCall} />
) : (
<Button
variant="contained"
color="primary"
onClick={() => setInCall(true)}
>
Join Call
</Button>
)}
</div>
);
}
export default App;
Controls.js
import { useState } from 'react';
import { useClient } from './settings';
import { Grid, Button } from '#material-ui/core';
import MicIcon from '#material-ui/icons/Mic';
import MicOffIcon from '#material-ui/icons/MicOff';
import VideocamIcon from '#material-ui/icons/Videocam';
import VideocamOffIcon from '#material-ui/icons/VideocamOff';
import ExitToAppIcon from '#material-ui/icons/ExitToApp';
export default function Controls(props) {
const client = useClient();
const {tracks, setStart, setInCall} = props;
const [trackState, setTrackState] = useState({video: true, audio: true});
const mute = async(type)=> {
if(type === "audio") {
await tracks[0].setEnabled(!trackState.audio);
setTrackState((ps) => {
return {...ps, audio: !ps.audio}
})
}
else if(type === "video") {
await tracks[1].setEnabled(!trackState.video);
setTrackState((ps) => {
return {...ps, video: !ps.video}
})
}
}
const leaveChannel = async() => {
await client.leave();
client.removeAllListeners();
tracks[0].close();
tracks[1].close();
setStart(false);
setInCall(false);
}
return (
<Grid container spacing={2} alignItems='center'>
<Grid item>
<Button variant="contained" color={trackState.audio ? "primary" : "secondary"} onclick={() => mute("audio")}>
{trackState.audio ? <MicIcon/> : <MicOffIcon/>}
</Button>
</Grid>
<Grid item>
<Button variant="contained" color={trackState.video ? "primary" : "secondary"} onclick={() => mute("video")}>
{trackState.video ? <VideocamIcon/> : <VideocamOffIcon/>}
</Button>
</Grid>
<Grid item>
<Button variant="contained" color="default" onclick={() => leaveChannel()}>
Leave <ExitToAppIcon/>
</Button>
</Grid>
</Grid>
)
}
Video.js
import { AgoraVideoPlayer } from "agora-rtc-react";
import { Grid } from "#material-ui/core";
import { useState, useEffect } from "react";
export default function Video(props) {
const { users, tracks } = props;
const [gridSpacing, setGridSpacing] = useState(12);
useEffect(() => {
setGridSpacing(Math.max(Math.floor(12 / (users.length + 1)), 4));
}, [users, tracks]);
return (
<Grid container style={{ height: "100%" }}>
<Grid item xs={gridSpacing}>
<AgoraVideoPlayer
videoTrack={tracks[1]}
style={{ height: "100%", width: "100%" }}
/>
</Grid>
{users.length > 0 &&
users.map((user) => {
if (user.videoTrack) {
return (
<Grid item xs={gridSpacing}>
<AgoraVideoPlayer
videoTrack={user.videoTrack}
key={user.uid}
style={{ height: "100%", width: "100%" }}
/>
</Grid>
);
} else return null;
})}
</Grid>
);
}
Without more detailed code, we can't help you. You probably broke a rule regarding react hooks.
The official docs state the following:
There are three common reasons you might be seeing it:
You might have mismatching versions of React and React DOM.
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app.
Read more about it here:
https://reactjs.org/warnings/invalid-hook-call-warning.html
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;
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>
)
}
}