ReactJS - item doesn't append instantly only refreshing page - node.js

I'm using ReactJS, NodeJS, MongoDB.
In my project I have a Task List and I'm adding new tasks (this works!) but only appends/show that new task when I refresh the page but I'm using ReactJS so I can have a more responsive/interactive website but I'm new at this and I'm still learning and I don't know what to do...Maybe I have to make something with the state?!
Hope you can help me! Thanks!
Here's my NewTask Component:
import React, { Component } from 'react';
import './NewTask.css';
class NewTask extends Component {
constructor(props) {
super(props);
this.state = {
projectId: null,
tasktitle: '',
taskcomment: ''
};
}
postDataHandler = () => {
let data = {
tasktitle: this.state.tasktitle,
taskcomment: this.state.taskcomment
};
fetch(`/dashboard/project/${this.props.projectId}/tasks/newtask`, {
method: 'POST',
data: data,
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
}).then(response => { return response.json() })
.catch(error => console.error('Error:', error));
}
render() {
return (
<div>
<input type='text' className='form-control input--task' required placeholder='Task Title' value={this.state.tasktitle} name='tasktitle' ref='tasktitle' onChange={(event) => this.setState({ tasktitle: event.target.value })} />
<button type='submit' className='btn btn-default button--newtask' value='Submit' onClick={this.postDataHandler}>Add Task</button>
</div>
);
}
}
export default NewTask;
Here's server side to create new task
//Create New Task
exports.create_new_task = (req, res) => {
let projectid = req.params.id;
Task.create({
tasktitle: req.body.tasktitle,
taskcomment: req.body.taskcomment,
project: req.params.id
}, (err, tasks) => {
if (err) {
console.log(err);
}
Project.findById(projectid, (err, project) => {
if(err) {
console.log(err);
}
project.tasks.push(tasks._id);
project.save();
console.log('NEW Task added to project: ' + projectid)
res.json(tasks)
});
});
};
Here's my Tasks Component
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTrashAlt, faEdit } from '#fortawesome/free-solid-svg-icons'
import './Tasks.css';
class Tasks extends Component {
constructor(props) {
super(props);
this.state = {
projectId: props._id,
tasks: []
};
}
componentDidMount() {
fetch(`/dashboard/project/${this.props.projectId}/tasks`)
.then(response => {
return response.json()
}).then(task => {
this.setState({
tasks: task.tasks
})
}).catch(error => console.error('Error:', error));
}
render() {
const fontawesomeiconStyle = {
fontSize: '1em',
color: '#8e8359',
textAlign: 'center'
}
const listStyle = {
display:'grid',
gridTemplateColumns:'2fr 1fr',
alignItems: 'center',
justifyItems: 'center'
}
const { tasks } = this.state;
return (
<div>
<ul className="task-list">
{tasks.map(task =>
<li key={task._id} style={listStyle}>{task.tasktitle}
<div>
<form method='POST' action={`/dashboard/project/${this.props.projectId}/tasks/delete?_method=DELETE&taskId=${task._id}`}>
<button className="button--tasks" >
<FontAwesomeIcon style={fontawesomeiconStyle} icon={faTrashAlt} />
</button>
</form>
</div>
</li>
)}
</ul>
</div>
);
}
}
export default Tasks;
Here's a gif so you can see what's really happening, only appends the
new task when I refresh the page..

You can return a task object from your POST method and then append to the existing task list. Something like this:
postDataHandler = () => {
/* removed for brevity */
.then(response => response.json())
.then(response => {
// append to existing list of tasks
this.props.appendTask(response);
})
.catch(error => console.error('Error:', error));
}
// method in parent component
// passed down through props
appendTask = task => {
let tasks = [...this.state.tasks];
tasks.push(task);
this.setState({tasks});
}
Your list will only re-render when a change in state affects what's being rendered. You either need to re-fetch the full list of tasks or manually append your new task, which is what's being done in the above example.
Here is a more complete example:
class TaskList extends Component {
constructor(props) {
super(props);
this.state = {
tasks: [
{/* task 1 */},
{/* task 2 */}
]
}
}
appendTask = task => {
let tasks = [...this.state.tasks];
tasks.push(task);
this.setState({tasks});
}
render() {
const { tasks } = this.state;
return (
<div className="tasks">
<ul>
{tasks.map(task => <TaskItem task={task}/>)}
</ul>
<NewTask appendTask={this.appendTask}/>
</div>
);
}
}
class NewTask extends Component {
/* ... */
postDataHandler = () => {
/* ... */
.then(response => response.json())
.then(response => {
// append to existing list of tasks
this.props.appendTask(response);
})
.catch(error => console.error('Error:', error));
}
/* ... */
}

After you POST the new item your have to GET the new item as well in your item list component.
You could put both the NewTask and TaskList components in one class component that could perform a GET after the POST promise resolves and update the state with the new item.
Or you could use Redux or another state handler that would use actions that trigger things in the right order.

Look, you're making a POST request to the backend, right?
As it seems, it gets stored correctly, but you're NOT doing anything with it. One way is to do it in a similar fashion as #wdm suggested, or just append the 'task' to your current state using setState, but only if it was posted in the first place, right? :)
Make sure that the response from the backend is the data you posted, use that response and append it to the already existing state using the ... spread operator. The setState will trigger a re-render and you'll have all your tasks listed.

Related

How to put data from json response in an array in reactjs

im trying to show images from the database and loop through them with a map. Here is the code:
class Container extends React.Component
{
state ={
userData:[]
}
fethData= async()=>{
fetch("http://localhost:5000/user") // could be any rest get url
.then(response => response.json())
.then(result =>
this.setState({
userData: result
})
);
}
componentDidMount() {
this.fethData();
alert(this.state.userData);
$(function(){
//Make every clone image unique.
var counts = [0];
var resizeOpts = {
handles: "all" ,autoHide:true
};
$(".dragImg").draggable({
helper: "clone",
//Create counter
start: function() { counts[0]++; }
});
$("#dropHere").droppable({
drop: function(e, ui){
if(ui.draggable.hasClass("dragImg")) {
$(this).append($(ui.helper).clone());
//Pointing to the dragImg class in dropHere and add new class.
$("#dropHere .dragImg").addClass("item-"+counts[0]);
$("#dropHere .img").addClass("imgSize-"+counts[0]);
//Remove the current class (ui-draggable and dragImg)
$("#dropHere .item-"+counts[0]).removeClass("dragImg ui-draggable ui-draggable-dragging");
$(".item-"+counts[0]).dblclick(function() {
$(this).remove();
});
make_draggable($(".item-"+counts[0]));
$(".imgSize-"+counts[0]).resizable(resizeOpts);
}
}
});
var zIndex = 0;
function make_draggable(elements)
{
elements.draggable({
containment:'parent',
start:function(e,ui){ ui.helper.css('z-index',++zIndex); },
stop:function(e,ui){
}
});
}
});
}
changeColor(params) {
this.setState({
color: params.target.value
})
}
changeSize(params) {
this.setState({
size: params.target.value
})
}
render() {
return (
<div className="container">
<div className="tools-section">
<div className="color-picker-container">
Select Brush Color :
<input type="color" value={this.state.color} onChange={this.changeColor.bind(this)}/>
</div>
<div className="brushsize-container">
Select Brush Size :
<select value={this.state.size} onChange={this.changeSize.bind(this)}>
<option> 5 </option>
<option> 10 </option>
<option> 15 </option>
<option> 20 </option>
<option> 25 </option>
<option> 30 </option>
</select>
</div>
</div>
<div className="board-container">
<h4>Select picture!</h4>
{this.state.userData.map((data) => (
<div class="dragImg">
<img src={data.picture} class="img"/> // column data received
</div>
))}
<div id="dropHere">
<Board color={this.state.color} size={this.state.size}></Board></div>
</div>
</div>
);
}
}
export default Container
I would like to put data from function fethData into userData array. But when i run the website i get an alert that userData is undefined. Why is nothing added to userData?
This is the json data fetched from the database:
[{"idpictures":1,"picture":"images/kitten.jpg","title_picture":"Cat"},{"idpictures":2,"picture":"images/puppy.jpg","title_picture":"Dog"}]
I would like the data to be stored like this:
userData:[{idpictures:1,picture:"images/kitten.jpg",title_picture:"Cat"}]
Guys I solved it. This is what I changed about my code:
constructor(){
super();
this.state ={
userData:[]
}
}
async componentDidMount() {
const url = "http://localhost:5000/user";
const response = await fetch(url);
const data = await response.json();
this.setState({userData: data});
console.log(this.state.userData);
if (this.state.userData) {alert(this.state.userData)}
You should define the state of the component in the constructor. Also all data fetching in JS is asynchronous. Notice the .then in the fetch function you wrote. It contains code that will be executed once the response comes back.
class Container extends React.Component
{
constructor(props) {
super(props)
this.state ={
userData:[]
}
}
fethData = async() => {
fetch("http://localhost:5000/user") // could be any rest get url
.then(response => response.json()) // you might not need this, depends on the response
.then(result =>
this.setState({
userData: result
})
);
}
componentDidUpdate() {
alert(this.state) // This should show your data (when it gets here)
}
componentDidMount() {
this.fethData();
alert(this.state.userData); // this will fire before the response from localhost:5000/user gets here
}
}
export default Container
Here is the working example
fethData= ()=>{
return new Promise((resolve, reject)=>{
fetch("http://localhost:5000/user") // could be any rest get url
.then(response => response.json())
.then(result =>
this.setState({userData: result},()=>{
resolve();
})
);
})
}
async componentDidMount(){
await this.fetchData();
alert(this.state.userData);
}
ReactJS is different from Vue, use: this.state.userData to access userData. And fetchData is a asynchronous function, you can't get it's result synchronously.

How can I initiate react to reload after a data change on the node js side?

So I added a calendar to react which changes the workout data to a specific date. When I change the date the data does change correctly. If I refresh the react web pages I get the updated data. I'm just a little confused on how to have node js initiate the data update in react. After changing the data I have node emitting to this socket...
import { format } from 'date-fns'
import ScrapeExperienceLevel from './ScrapeExperienceLevel'
export default (websockets, queue, timer, workouts, scrapeExperienceLevel) => {
websockets.on('connection', socket => {
socket.on('joinRoom', roomName => {
socket.join(roomName)
})
socket.on('leaveRoom', roomName => {
socket.leave(roomName)
})
setInterval(function(){
socket.emit('GetCheckedInMemebers', queue.dump());
}, 3000);
socket.on('initScreen', screen => {
const screenWorkouts = workouts.current.filter(workout => workout.station == screen)
socket.emit('screenInfo', screenWorkouts)
socket.emit('queueInfo', queue.screen(screen - 1))
socket.emit('timer', { time: timer.formatTime().formattedTime })
})
// code in question
socket.on('GetWorkoutDate', async function (date) {
await workouts.getNewWorkouts(date.date)
const screenWorkouts = workouts.current.filter(workout => workout.station == 1)
socket.emit('UpdateWorkouts', screenWorkouts)
})
socket.on('initAdmin', () => {
socket.emit('queue', queue.dump())
socket.emit('timer', { time: timer.formatTime().formattedTime })
})
socket.on('initOverview', () => {
socket.emit('workouts', workouts.current)
socket.emit('queue', queue.dump())
socket.emit('timer', { time: timer.formatTime().formattedTime })
})
socket.on('addUser', async (person) => {
if(typeof person.member_id == 'undefined')
person.member_id = ''
else{
person.experiencelevel = await scrapeExperienceLevel.getExperienceLevel(person.member_id)
person.firstname = person.firstname + ' '
}
queue.add(person, timer)
websockets.emit('queue', queue.dump())
})
socket.on('removeUser', ({ group, person, list }) => {
queue.remove(group, person, list)
websockets.emit('queue', queue.dump())
})
socket.on('reorder', waiting => {
queue.reorder(waiting)
websockets.emit('queue', queue.dump())
})
socket.on('toggleTimer', () => {
if (timer.isRunning()) {
timer.pause()
} else {
timer.start()
}
})
})
}
Here's my Screen page's react...
import React, { Component } from 'react'
import { defaultTo } from 'ramda'
import './styles.css'
class Screen extends Component {
state = {
time: '',
rest: false,
workout: [],
queue: [],
}
UpdateTimer = ({ time, rest }) => {
this.setState({ time, rest })
}
UpdateScreenInfo = ({ data }) => {
this.setState({ workout: defaultTo({}, data[0]) })
}
UpdateQueueInfo = ({ data }) => {
this.setState({ queue: defaultTo({}, data) })
}
UpdateQueue = ({ queue, screenNumber }) => {
this.setState({ queue: defaultTo({}, queue[screenNumber - 1]) })
}
componentDidMount() {
const screenNumber = this.props.match.params.id
const { socket } = this.props
socket.on('connect', () => {
socket.emit('joinRoom', 'screens')
socket.emit('initScreen', screenNumber)
})
socket.on('timer', ({ time, rest }) => this.UpdateTimer({ time, rest }))
socket.on('screenInfo', data => this.UpdateScreenInfo({ data }))
socket.on('queueInfo', data => this.UpdateQueueInfo({ data }))
socket.on('queue', ({ queue }) => this.UpdateQueue({ queue, screenNumber }))
//socket.on('UpdateWorkouts', (updatedData) => this.UpdateWorkoutsData(updatedData))
}
componentWillUnmount() {
const { socket } = this.props
socket.off('timer', this.UpdateTimer)
socket.off('screenInfo', this.UpdateScreenInfo)
socket.off('queueInfo', this.UpdateQueueInfo)
socket.off('queue', this.UpdateQueue)
}
renderMovement = (movement, equipment) => {
if (!movement) return <noscript />
return (
<div className="screenMove">
{equipment && `${equipment.title} `}
{movement.title}
</div>
)
}
render() {
const { time, rest, queue } = this.state
const { workout } = this.props
const variation = defaultTo({}, workout.variation)
const person1 = defaultTo({}, queue[0])
const person2 = defaultTo({}, queue[1])
return (
<main className="screenWrapper">
<div className="screenHeader">
<span className="screenFirstUser">
{(() => {
if (person1.experiencelevel === 'novice') {
// light purple
return (
<div style={{color:'#BF5FFF', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
} else if (person1.experiencelevel === 'beginner') {
// light blue
return (
<div style={{color:'#87CEFA', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
} else if (person1.experiencelevel === 'intermediate') {
return (
<div style={{color:'orange', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
} else if (person1.experiencelevel === 'advanced') {
// gym green
return (
<div style={{color:'#93C90E', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
}else if (person1.experiencelevel === 'expert') {
return (
<div style={{color:'red', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
}
})()}
</span>
<span className={`screenTimer alt ${rest ? 'rest' : ''}`}>{time ? time : '0:00'}</span>
<span className="screenSecondUser">
{(() => {
if (person2.experiencelevel === 'novice') {
// light purple
return (
<div style={{color:'#BF5FFF', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
} else if (person2.experiencelevel === 'beginner') {
// light blue
return (
<div style={{color:'#87CEFA', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
} else if (person2.experiencelevel === 'intermediate') {
return (
<div style={{color:'orange', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
} else if (person2.experiencelevel === 'advanced') {
// gym green
return (
<div style={{color:'#93C90E', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
}else if (person2.experiencelevel === 'expert') {
return (
<div style={{color:'red', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
}
})()}
</span>
</div>
<p className="screenVariation">{variation.title}</p>
<div className="screenMoves">
{this.renderMovement(workout.movementOne, workout.equipmentOne)}
{this.renderMovement(workout.movementTwo, workout.equipmentTwo)}
{this.renderMovement(workout.movementThree, workout.equipmentThree)}
</div>
</main>
)
}
}
export default Screen
Also found the parent component Daniele enlightened me about. Now I'm sharing 1 socket connection throughout all the components.
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { path } from 'ramda'
import { connect, Provider } from 'react-redux'
import ReactDOM from 'react-dom'
import { BrowserRouter, Route, Redirect, Switch } from 'react-router-dom'
import openSocket from 'socket.io-client'
import { defaultTo } from 'ramda'
import './index.css'
import 'semantic-ui-css/semantic.min.css'
import store from './redux/store'
import Admin from './screens/Admin'
import Screens from './screens/Screens'
import Auth from './screens/Auth'
import BackScreen from './screens/Screens/Back'
import FrontScreen from './screens/Screens/Front'
const socket = openSocket(`http://${window.location.hostname}:${process.env.REACT_APP_WEBSOCKET_PORT}`)
function mapState(state) {
return {
loggedIn: path(['conf', 'loggedIn'], state),
}
}
class App extends Component {
state = {
workout: []
}
static propTypes = {
loggedIn: PropTypes.bool,
}
componentDidMount() {
socket.on('UpdateWorkouts', (workout) => { console.log(workout[0]); this.setState({ workout: defaultTo({}, workout[0]) }) })
}
render() {
const { loggedIn } = this.props
const workout = this.state
return (
<BrowserRouter>
<Switch>
{!loggedIn && <Route path="/" component={(props) => <Auth socket={socket} {...props} /> }/> }
<Route exact path="/admin" component={(props) => <Admin socket={socket} {...props} /> } />
<Route path="/s/back" component={(props) => <BackScreen socket={socket} {...props} /> } />
<Route path="/s/front" component={(props) => <FrontScreen socket={socket} {...props} /> } />
<Route path="/s/:id" component={(props) => <Screens {...props} socket={socket} workout={workout} /> } />
<Redirect to={'/s/1'} />
</Switch>
</BrowserRouter>
)
}
}
const ConnectedApp = connect(mapState)(App)
const rootEl = document.getElementById('root')
ReactDOM.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
rootEl,
)
Here's my Admin page.
import React, { Component, Fragment } from 'react'
import { Button, Grid, Header, Dimmer, Loader } from 'semantic-ui-react'
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { defaultTo } from 'ramda'
import './styles.css'
import WaitingList from './WaitingList'
import Stations from './Stations'
import AddUserModal from './AddUserModal'
class Admin extends Component {
state = {
loading: false,
time: '',
rest: false,
data: {
waiting: [],
queue: [],
},
startDate: new Date(),
showAddUserModal: false,
}
handleChange = date => {
const { socket } = this.props
this.setState({
startDate: date
})
socket.emit('GetWorkoutDate', { date })
}
UpdateTimer = ({ time, rest }) => {
this.setState({ time, rest })
}
UpdateQueue = ({ data }) => {
this.setState({ queue: defaultTo({}, data) })
}
GetCheckedInMemebers = ({ data }) => {
this.setState({ data })
}
componentDidMount() {
const { socket } = this.props
socket.on('connect', () => {
socket.emit('initAdmin')
})
socket.on('timer', ({ time, rest }) => this.UpdateTimer({ time, rest }))
socket.on('queue', data => this.UpdateQueue({ data }))
socket.on('GetCheckedInMemebers', data => this.GetCheckedInMemebers({ data }))
}
componentWillUnmount() {
const { socket } = this.props
socket.off('timer', this.UpdateTimer)
socket.off('queue', this.UpdateQueue)
socket.off('GetCheckedInMemebers', this.GetCheckedInMemebers)
}
addPersonToWaitingList = person => {
const { socket } = this.props
socket.emit('addUser', person)
}
removePersonFromList = (groupIndex, personIndex, list) => {
const { socket } = this.props
socket.emit('removeUser', { group: groupIndex, person: personIndex, list: list })
}
reorderWaitingList = waiting => {
const { data } = this.state
const { socket } = this.props
this.setState({ data: { ...data, waiting } })
socket.emit('reorder', waiting)
}
toggleTimer = () => {
const { socket } = this.props
socket.emit('toggleTimer')
}
render() {
const { loading, rest, time, data, showAddUserModal } = this.state
return (
<Fragment>
change workout date <DatePicker
dateFormat="M/d/yy"
selected={this.state.startDate}
onChange={this.handleChange}
/>
<Grid container columns={2} divided id="adminWrapper">
<Grid.Row className="fGrow">
<Grid.Column className="listWrapper">
<Header size="huge" textAlign="center">
Queue
<Button positive floated="right" onClick={() => this.setState({ showAddUserModal: true })}>
Add
</Button>
</Header>
<WaitingList
className="adminList"
waiting={data.waiting}
reorder={this.reorderWaitingList}
removeFromList={this.removePersonFromList}
/>
</Grid.Column>
<Grid.Column className="listWrapper">
<Header size="huge" textAlign="center">
Stations
</Header>
<Stations className="adminList" queue={data.queue}
removeFromList={this.removePersonFromList}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Button fluid color={rest ? 'red' : 'blue'} onClick={this.toggleTimer}>
<span>{time ? time : '0:00'}</span>
</Button>
</Grid.Row>
</Grid>
<AddUserModal
show={showAddUserModal}
handleClose={() => this.setState({ showAddUserModal: false })}
addUser={this.addPersonToWaitingList}
setLoading={isLoading => this.setState({ loading: isLoading })}
loading={loading}
/>
<Dimmer active={loading} page>
<Loader />
</Dimmer>
</Fragment>
)
}
}
export default Admin
New to node and react. Appreciate the guidance!
Edit: The calendar (date picker) is on another react page. GetWorkoutDate is being called. However, react never gets the data from the emit socket.emit('UpdateWorkouts', screenWorkouts). I verified the everything works correctly except getting the new data to update the react state.
Final Edit:
So for some reason I was not able to send the workout through props for the Screen page. Although, it is the correct way for react there just must be something going on with my environment. Here's what I did get working for me. When Screen page loads it loads the workout. Then I just added settimeout to repeat getting the workout every 5 secs.
socket.on('initScreen', async screen => {
setInterval(async function(){
const screenWorkouts = await workouts.current.filter(workout => workout.station == screen)
socket.emit('screenInfo', screenWorkouts)
}, 5000)
socket.emit('queueInfo', queue.screen(screen - 1))
socket.emit('timer', { time: timer.formatTime().formattedTime })
})
What's weird is after sending the workout to screens between 3 and 8 times correctly eventually it sends an empty array. So to prevent the workout from updating when it's empty I check it for length before I try and update the state.
UpdateScreenInfo = ({ data }) => {
if(data.length !== 0)
this.setState({ workout: defaultTo({}, data[0]) })
I'm awarding Daniele the points since they are about to expire and Daniele helped so much!
The problem is that each time you call openSocket you open a new connection to the server. Looking at your server code it seems that your 'GetWorkoutDate' message handler replies with 'UpdateWorkouts' message on the same socket connection; the problem is that 'UpdateWorkouts' is received by the react component sending the 'GetWorkoutDate' message and not by other react components due they open a new connection each.
The proper way to handle with WebSockets in a react app where more components needs to access the WebSocket is to open it only in the root component and passing it to child components as a property. Something like:
MainComponent extends React.Component {
componentDidMount() {
// Open the socket only in the main component
this.socket = openSocket(...);
}
componentWillUnmount() {
this.socket.close();
}
render() {
return (
<div>
{/* pass the socket as property to child components;
this can be repeated with all nested sub-component */}
<ChildComponent socket={this.socket}>
</div>
)
}
}
ChildComponent extends React.Component {
componentDidMount() {
const { socket } = this.props;
// create the required handlers and store them
this.handler = data => { /* do what you need with data */ };
// add the handlers to the socket
socket.on("message", this.handler);
}
componentWillUnmount() {
const { socket } = this.props;
// remove the handlers from the socket
socket.off("message", this.handler);
}
}
By this way you open a single connection and you share it between all components, now you can send messages to the server from a component and handle the response message on any other component.
Edit after last question update:
Two things:
the parent component is the root App component ok, but I can't still understand which is the component which emits the 'GetWorkoutDate' message (I was thinking it was the Screen 's parent component, but it seems I'm wrong); probably to find final solution we need to clarify this;
you are passing the socket (as a property) to the Route component, not to the Screen component: you need to change your routes as follows
<Route path="/s/:id" component={() => <Screens socket={socket} />} />
I checked better last version of your Screen component: you can't do this in componentDidMount method:
socket.on('timer', ({ time, rest }) => this.setState({ time, rest }))
you need to store the reference of the handler function in order to remove it later in componentWillUnmount method.
You can't do this in componentWillUnmount method:
socket.close();
now you have only one connection shared between all components of your app, if you close it once, you close it forever.
The strategy is: in componentDidMount method:
for each message (or more on general for each event), to create a handler function and store its reference
attache the referenced handler function to the desired message (or more in general, to the desired event)
then, in componentWillUnmount method:
to detach the handler function (throug the reference we still have) from the message (or more in general from the event),
by this way each time the component is mounted it starts listening on the desired messages/events, each time the component is unmounted it stops doing it and no actions will be performed on it while not mounted be the message/event handlers.
Edit:
Having circular import dependency is a bad idea: it's better to remove any var App = require('./index') (or similar) from child component files
But moreover, if the purpose of handleChange is only to emit something on the ws you don't need a so complicated design pattern: you can access the socket from Admin Component.
Probably what you need is
class App extends Component {
componentDidMount() {
// App component will never unmount: the reference for unmounting is not required
socket.on('UpdateWorkouts', ([workout]) => this.setState({ workout }));
}
render() {
const { workout } = this.state;
return (
...
<Route path="/s/:id" component={props => <Screens {...props, socket, workout} />} />
...
);
}
}
class Screen extends Component {
render() {
const { time, rest, queue } = this.state;
const { workout } = this.props;
...
}
}
You are calling componentDidMount function when socket emits UpdateWorkouts which is causing the loop. You should not call componentDidMount function of a react component, react handles the lifecycles methods itself. You can have another method to handle updates as following.
handleUpdateWorkouts = (updatedData) => {
//Update your workout, queue etc. whatever you update
this.setState({updatedData})
}
And assign this function as callback to on "UpdateWorkouts" events.
this.socket.on('UpdateWorkouts', (updatedData) => this.handleUpdateWorkouts(updatedData));
Make sure you are emitting GetWorkoutDate properly upon the calendar onChange.
this.socket.on('GetWorkoutDate', date); //something like this
If getNewWorkouts function is an async, then use await
socket.on('GetWorkoutDate', async function (date) {
await workouts.getNewWorkouts(date.date) //<----- here
const screenWorkouts = workouts.current.filter(workout => workout.station == 1)
console.log(screenWorkouts[0])
socket.emit('UpdateWorkouts', screenWorkouts)
})
I do have one solution at architect level which is, Return synchronous actions from the nodeJs sockets. So the moment client receive it, just dispatch the action received via socket and then automatically update it.
For eg: if socket returns
{
type: 'actionName',
data
}
then dispatching it directly would result in auto update of subscribed components.

How to update the user feed after updating the post?

I have a UserFeed component and EditForm component. As soon as I edit the form, I need to be redirected to the UserFeed and the updated data should be shown on UserFeed(title and description of the post).
So, the flow is like-UserFeed, which list the posts, when click on edit,redirected to EditForm, updates the field, redirected to UserFeed again, but now UserFeed should list the posts with the updated data, not the old one.
In this I'm just redirectin to / just to see if it works. But I need to be redirected to the feed with the updated data.
EditForm
import React, { Component } from "react";
import { connect } from "react-redux";
import { getPost } from "../actions/userActions"
class EditForm extends Component {
constructor() {
super();
this.state = {
title: '',
description: ''
};
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
})
};
componentDidMount() {
const id = this.props.match.params.id
this.props.dispatch(getPost(id))
}
componentDidUpdate(prevProps) {
if (prevProps.post !== this.props.post) {
this.setState({
title: this.props.post.post.title,
description: this.props.post.post.description
})
}
}
handleSubmit = () => {
const id = this.props.match.params.id
const data = this.state
this.props.dispatch(updatePost(id, data, () => {
this.props.history.push("/")
}))
}
render() {
const { title, description } = this.state
return (
<div>
<input
onChange={this.handleChange}
name="title"
value={title}
className="input"
placeholder="Title"
/>
<textarea
onChange={this.handleChange}
name="description"
value={description}
className="textarea"
></textarea>
<button>Submit</button>
</div>
);
}
}
const mapStateToProps = store => {
return store;
};
export default connect(mapStateToProps)(EditForm)
UserFeed
import React, { Component } from "react"
import { getUserPosts, getCurrentUser } from "../actions/userActions"
import { connect } from "react-redux"
import Cards from "./Cards"
class UserFeed extends Component {
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch(getCurrentUser(authToken))
if (this.props && this.props.userId) {
this.props.dispatch(getUserPosts(this.props.userId))
} else {
return null
}
}
}
render() {
const { isFetchingUserPosts, userPosts } = this.props
return isFetchingUserPosts ? (
<p>Fetching....</p>
) : (
<div>
{userPosts &&
userPosts.map(post => {
return <Cards key={post._id} post={post} />
})}
</div>
)
}
}
const mapStateToPros = state => {
return {
isFetchingUserPosts: state.userPosts.isFetchingUserPosts,
userPosts: state.userPosts.userPosts.userPosts,
userId: state.auth.user._id
}
}
export default connect(mapStateToPros)(UserFeed)
Cards
import React, { Component } from "react"
import { connect } from "react-redux"
import { compose } from "redux"
import { withRouter } from "react-router-dom"
class Cards extends Component {
handleEdit = _id => {
this.props.history.push(`/post/edit/${_id}`)
}
render() {
const { _id, title, description } = this.props.post
return (
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<img
src="https://bulma.io/images/placeholders/96x96.png"
alt="Placeholder image"
/>
</figure>
</div>
<div className="media-content" style={{ border: "1px grey" }}>
<p className="title is-5">{title}</p>
<p className="content">{description}</p>
<button
onClick={() => {
this.handleEdit(_id)
}}
className="button is-success"
>
Edit
</button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
nothing: "nothing"
}
}
export default compose(withRouter, connect(mapStateToProps))(Cards)
updatePost action
export const updatePost = (id, data, redirect) => {
return async dispatch => {
dispatch( { type: "UPDATING_POST_START" })
try {
const res = await axios.put(`http://localhost:3000/api/v1/posts/${id}/edit`, data)
dispatch({
type: "UPDATING_POST_SUCCESS",
data: res.data
})
redirect()
} catch(error) {
dispatch({
type: "UPDATING_POST_FAILURE",
data: { error: "Something went wrong"}
})
}
}
}
I'm not sure if my action is correct or not.
Here's the updatePost controller.
updatePost: async (req, res, next) => {
try {
const data = {
title: req.body.title,
description: req.body.description
}
const post = await Post.findByIdAndUpdate(req.params.id, data, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}
One mistake is that to set the current fields you need to use $set in mongodb , also you want to build the object , for example if req.body does not have title it will generate an error
updatePost: async (req, res, next) => {
try {
const data = {};
if(title) data.title=req.body.title;
if(description) data.description=req.body.description
const post = await Post.findByIdAndUpdate(req.params.id, {$set:data}, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}

Redirect after a post request with data in react js / node js

I'm trying to send data to another component page after doing a post request to my node js server.
first component to redirect: (I cannot post all my code, it is to big
fetch('http://localhost:8080/createVolume', requestOptions)
.then(response => response.json())
.then(data => {
console.log(data);
this.setState({
ansibleAnswer: data,
submitted: true
})
console.log(this.state.ansibleAnswer);
});
.
.
.
// uderneeth my render
} else if (this.state.loaded === true && this.state.submitted === true) {
return (
<Redirect to={{
pathname: '/Completed',
state: { name: this.state.name, result: this.state.ansibleAnswer }
}}
/>
);
new component after post request:
class Completed extends React.Component {
constructor(props) {
super(props);
this.state = {
name: this.props.location.state.name,
result: this.props.location.state.ansibleAnswer
};
}
render() {
return (
<div>
<Header />
<div>
<p>test successful</p>
<p>{this.state.result}</p>
</div>
<Footer />
</div>
);
}
}
export default Completed;
I get this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
I understand that the data isn't saved in the state but I don't understand how I could save it (or wait) before sending the data to the other component.
thank you
class Completed extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
result: ''
};
}
/** When the Completed component does anything with the new props,
* componentWillReceiveProps is called, with the next props as the argument */
componentWillReceiveProps(nextProps) {
/** Update state**/
if (nextProps.location.state.result !==
this.props.location.state.result) {
this.setState({
result:nextProps.location.state.result
});
}
}
render() {
return (
<div>
<Header />
<div>
<p>test successful</p>
<p>{this.state.result}</p>
</div>
<Footer />
</div>
);
}
}
export default Completed;

Component are not update after redirect back from Nodejs

I've created small ReactJS app and get retrieve user feeds data from facebook api.
If data not shown, call NodeJS api and fetch feeds from facebook and redirect back to index screen. Problem is once redirect back, I found that feeds is already in database and after redirect back to index page, feeds are not shown, I need to reload browser screen.
My problem is which component should I use after redirect back to original screen in react?
import React, { Component } from 'react';
import ReloadButton from './ReloadButton';
import Feeds from './Feeds';
import Alert from './Alert';
class MyTest extends Component {
constructor(props) {
super(props);
this.state = {
feeds: []
};
}
componentDidMount() {
fetch('/fetch')
.then(response => response.json())
.then(data => this.setState({ feeds: data }));
}
render() {
const { feeds } = this.state;
return (
<div className="container-fluid">
<a className="btn btn-primary" href="/auth/facebook">Reload</a>
{ feeds.length > 0 ? <Feeds feeds={ feeds } /> : <Alert /> }
</div>
);
}
}
export default MyTest;
if I were you I did something like this. please say if it is helpful
interface ExampleProps {
someDataFacebookSendMe: any; //this prop used to get redirect from facebook and for first time is null
}
interface ExampleState {
feeds: []
spinning: boolean;
isDataCollected: boolean; // this proprty check for gotten data
}
export class Example extends React.Component<ExampleProps, ExampleState> {
constructor(props) {
super(props);
this.state = {
feeds: [],
spinning: true,
isDataCollected: false
};
}
componentDidMount() {
if (!!this.props.someDataFacebookSendMe) {
while (!this.state.isDataCollected) {
this.getData()
}
fetch('/fetch')
.then(response => response.json())
.then(data => {
if (data !== null && data !== undefined) {
this.setState({ feeds: data, spinning: false, isDataCollected: true })
}
});
}
else {
this.setState({spinning: false})
}
}
getData() {
fetch('/fetchIsDataExists') // this method check if data has gotten or not
.then(data => this.setState({isDataCollected: true }));
}
render() {
const { feeds } = this.state;
return (
<div className="container-fluid">
<Spin spinning={spinning} >
<a className="btn btn-primary" href="/auth/facebook">Reload</a>
{feeds.length > 0 ? <Feeds feeds={feeds} /> : <Alert />}
</Spin>
</div>
);
}
}

Resources