Update a list after an item triggers its own deletion - javascript

I am rendering a card in a parent component for every post that a user has. In the card, all of the data is passed down via props. I have a delete axios call that works, however I have to manually refresh the page for the page to show updates.
Any way I can have it manually update the UI?
// DASHBOARD.JS
if (this.state.posts.length > 0 && this.state.loaded === true) {
const posts = this.state.posts;
content = posts.map(post => (
<Card
key={post._id}
author={post.author}
title={post.title}
date={post.date}
body={post.body}
id={post._id}
/>
));
// CARD.JS
deleteOnClick = e => {
axios
.delete('http://localhost:5000/auth/deletePost', {
params: {
id: this.props.id
}
})
.then(res => {
console.log(res);
})
.catch(err => console.log(err));
};

I think you have two problems to fix in order to make this pattern work.
First thing first: avoid defining business logic in components used only for presentational purposes (have a read here).
So in Card component there should be no explicit definition of the deleteOnClick method, while it should receive it from above in a dedicated prop of type func.
Second thing: the list component should handle the logic of deleting items from the list through the axios call and in the then statement you should think of a way to update the list items you are using to render Cards.
Examples in pseudocode:
List Component
import React from 'react';
import Card from './Card';
export default class List extends PureComponent {
state = {
items: [],
error: null,
}
componentDidMount() {
// add axios call to retrieve items data
}
deleteItemHandler = () => {
axios
.delete('http://localhost:5000/auth/deletePost', {
params: {
id: this.props.id
}
})
.then(res => {
this.setState({
items: res.json(),
})
})
.catch(err => {
this.setState({
error: err,
})
});
};
}
render() {
const { items } = this.state;
return (
<div>
{items.map(item => (
<Card
{...item}
onClick={this.deleteItemHandler}
/>
))}
</div>
)
}
}
Card component:
import React from 'react';
import PropTypes from 'prop-types';
export default class Card extends PureComponent {
static propTypes = {
title: PropTypes.string,
// other props
onClick: PropTypes.func.isRequired,
}
// other things in this class
render() {
const { onClick, title } = this.props;
return (
<div onClick={onClick}>
<h1>{title}</h1>
</div>
)
}
}
Once you get familiar with concept of separating logic and presentation you can start introducing redux and do things at another level :)
Hope this helps!

Related

How to do a for loop with React and why is my Component not getting rendered

import React, { Component } from "react";
import Pokecard from "./Pokecard";
import "./Pokedex.css";
class Pokedex extends Component {
static defaultProps = {
pokemon: [],
getData() {
for (let i = 1; i <= 40; i++) {
fetch(`https://pokeapi.co/api/v2/pokemon/${i}`)
.then((response) => response.json())
.then((data) => {
this.pokemon.push({
id: data.id,
namePoke: data.name,
type: data.types[0].type.name,
base_experience: data.base_experience,
});
});
}
},
};
render() {
this.props.getData();
return (
<div className="Pokedex">
<div className="Pokedex-grid">
{this.props.pokemon.map((p) => (
<Pokecard
id={p.id}
name={p.namePoke}
type={p.type}
exp={p.base_experience}
key={p.id}
/>
))}
</div>
</div>
);
}
}
export default Pokedex;
I am new to React and I don't understand why my for loop runs multiple times so I get the Pokecards twice or more.
Also, the whole Component Pokedex is not showing up when reloading the page. What do I do wrong?
#azium made a great comment. You are calling to get data in your render, which is setting state, and causing a re-render, which is calling getData again, which is fetching data again and then setting state again, and the cycle continues on and on indefinitely. Also, default props should only define properties default values, but in this case you don't need a getData default prop. All you need to do is call the getData method in your componentDidMount. And your method needs to store the data in state, and not do a direct property change (like you are doing). Here is an example:
import React, { Component } from "react";
import Pokecard from "./Pokecard";
import "./Pokedex.css";
class Pokedex extends Component {
static state = {
pokemon: []
};
componentDidMount() {
this.getData();
}
getData() {
for (let i = 1; i <= 40; i++) {
const pokemon = [...this.state.pokemon];
fetch(`https://pokeapi.co/api/v2/pokemon/${i}`)
.then((response) => response.json())
.then((data) => {
pokemon.push({
id: data.id,
namePoke: data.name,
type: data.types[0].type.name,
base_experience: data.base_experience,
});
});
this.setState({pokemon});
}
}
render() {
return (
<div className="Pokedex">
<div className="Pokedex-grid">
{this.state.pokemon.map((p) => (
<Pokecard
id={p.id}
name={p.namePoke}
type={p.type}
exp={p.base_experience}
key={p.id}
/>
))}
</div>
</div>
);
}
}
export default Pokedex;

Loading API data before Table render ReactJS

I use MobX to control my ReactJS state/components and I also use an async call through MobX in order to retrieve the data, this is typically called in my header throuhg componentDidMount().
(I already know my code isn't the cleanest and most likely has errors, cut me some slack as I'm learning/coding this completely on my own at this point with no educational background in programming)
import React from 'react';
import { Layout, Row, Col, Menu, Avatar, Tag } from 'antd';
import { inject, observer } from 'mobx-react';
import Icon from '#ant-design/icons';
const { Header } = Layout;
const { SubMenu } = Menu;
#inject('store')
#observer
class PageHeader extends React.Component {
componentDidMount() {
this.props.store.getOrders();
}
Say for instance I was not in the Application, but I was still logged in through my LocalStorage data, and I went to a page "http://localhost/orders/123456". 123456 would be my order ID and this page would display it's details. Now considering I was not on the page, the DOM wasn't rendered, right? Right... But I'm still logged in through my LocalStorage, so when I visit the page - it's rendering blank because MobX has to wait for the API call to retrieve the data. I need to be able to pull this data and make sure it's rendered on the page, so I some how need the API to be retrieve before rendering to ensure it's pull the data out of MobX with the specified OrderID, in this case 123456.
Below is two ways I've made my componentDidMount
#inject('store')
#observer
class LoadPage extends React.Component {
state = {
visible: false,
ordernum: this.props.match.params.id,
orderkey: null,
}
componentDidMount() {
document.title = this.props.match.params.id;
route_ordernum = this.props.match.params.id;
if (this.props.store.orders.length === 0) {
fetch('http://localhost:5000')
.then(res1 => res1.json())
.then(data => this.props.store.setOrders(data))
.then(this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
}))
}
if (this.props.store.orders.length > 0) {
this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
})
}
console.log(this.state.orderkey)
}
render() {
Example #2
componentDidMount() {
document.title = this.props.match.params.id;
route_ordernum = this.props.match.params.id;
if (this.props.store.orders.length === 0) {
this.props.store.getOrders().then(dataloads => {
this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
})
})
}
if (this.props.store.orders.length > 0) {
this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
})
}
console.log(this.state.orderkey)
}
I'm just passing through my MobX and using two separate classes to create my state now.
#inject('store')
#observer
class LoadMain extends React.Component {
render() {
return(
this.props.store.orders.length === 0 ? <Content style={{ backgroundColor: "#ffffff" }}><center><Spinner /></center></Content> : <OrderPage ordernum={this.props.match.params.id} />
);
}
}

Assigning a value inside a function and using it outside

I'm writing a reaction web application with firebase backend. Based on field query of one document, I wanted to access another document.
I'm only able to return the query and directly use it for accessing the document. It's making a call to the function and query over and over again.
import React, { Component } from 'react';
import { compose } from 'recompose';
import { withAuthorization } from '../Session';
import { withStyles } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import './Image/1.jpeg'
import Album from './Album.js';
const styles = theme => ({
root: {
...theme.mixins.gutters(),
paddingTop: theme.spacing.unit * 2,
paddingBottom: theme.spacing.unit * 2,
},
});
class ChildPage extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
items: [],
};
this.classes = props;
}
componentDidMount() {
this.setState({ loading: true });
let query = [];
this.unsub = this.props.firebase.users().doc(this.props.firebase.userId()).get().then(doc => {
query.push(doc.data().LinkedUsername)
const lU = this.props.firebase.users().where("email", "==", query[0])
lU.get().then(snapshot => {
console.log(snapshot.docs[0].id)
})
})
this.unsubscribe = this.props.firebase
.users().doc(this.props.firebase.userId()).collection('tasks')
.onSnapshot(snapshot => {
let items = [];
snapshot.forEach(doc =>
(doc.data().status === false) ?
items.push({ ...doc.data(), uid: doc.id })
:
null
);
this.setState({
items,
loading: false,
});
});
}
componentWillUnmount() {
this.unsubscribe();
this.unsub();
}
removeItem(itemId) {
const itemRef = this.props.firebase.users().doc(`${itemId}`);
itemRef.remove();
}
render() {
return (
<div className='background'>
<div className='topBar'>
</div>
<Paper className={ this.classes.root } elevation={ 1 }>
<Album cards={ this.state.items } />
</Paper>
</div>
);
}
}
const condition = authUser => !!authUser;
export default compose(
withAuthorization(condition),
withStyles(styles),
)(ChildPage)
I want the query to run one time and assign the return value to a variable. Then be able to use that value to access them and load the documents.
Currently, this gives me a document id in the console. And if I want to use that id, I take the next two "})" brackets and put them before
"
}
componentWillUnmount() {"
and take everything inside the console i.e. "snapshot.docs[0].id" and put it inplace of "this.props.firebase.userId()" in the doc of this.unsubscribe.
But it's what calls the function over and over and gives the error,
"Warning: Encountered two children with the same key, [object Object]. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version."

AWS Appsync graphqlMutation helper not updating query

I'm following this tutorial: https://egghead.io/lessons/react-execute-mutations-to-an-aws-appsync-graphql-api-from-a-react-application
I have a simple todo react app hooked up to AppSync via amplify. The queries and mutations were autogenerated by Amplify.
Using the graphqlMutation helper, my query is supposed to be automatically updated after running my mutations, but it's not working. Upon refresh I do see the mutations are updating the AppSync backend, but I also expect it to update immediately with an optimistic response.
Here is the code:
import React, { Component } from "react";
import gql from "graphql-tag";
import { compose, graphql } from "react-apollo";
import { graphqlMutation } from "aws-appsync-react";
import { listTodos } from "./graphql/queries";
import { createTodo, deleteTodo } from "./graphql/mutations";
class App extends Component {
state = { todo: "" };
addTodo = async () => {
if (this.state.todo === "") {
return;
}
const response = await this.props.createTodo({
input: {
name: this.state.todo,
completed: false
}
});
this.setState({ todo: "" });
console.log("response", response);
};
deleteTodo = async id => {
const response = await this.props.deleteTodo({ input: { id } });
console.log("response", response);
};
render() {
return (
<div>
<div>
<input
onChange={e => this.setState({ todo: e.target.value })}
value={this.state.todo}
placeholder="Enter a name..."
/>
<button onClick={this.addTodo}>Add</button>
</div>
{this.props.todos.map(item => (
<div key={item.id}>
{item.name}{" "}
<button onClick={this.deleteTodo.bind(this, item.id)}>
remove
</button>
</div>
))}
</div>
);
}
}
export default compose(
graphqlMutation(gql(createTodo), gql(listTodos), "Todo"),
graphqlMutation(gql(deleteTodo), gql(listTodos), "Todo"),
graphql(gql(listTodos), {
options: {
fetchPolicy: "cache-and-network"
},
props: props => ({
todos: props.data.listTodos ? props.data.listTodos.items : []
})
})
)(App);
A repo containing the codebase is here: https://github.com/jbrown/appsync-todo
What am I doing wrong here that my query isn't updated?
Your input contains only properties name and completed. Tool graphqlMutation will add id automatically.
Code doesn't contains list query, I can guess than query requested for more data than name, completed and id.
So item will not be added to list because of missing required informations.
Solution is add all listed properties to createTodo.

How to use HOC and recompose properly

I just started React recently and read about HOC.
I would like to do something similar to: making a container editable.
My 'solution' (as it's not working properly yet.)
editableRow (HOC):
import React from 'react'
import { withStateHandlers, withHandlers, compose } from 'recompose'
const editableRow = () =>
compose(
withStateHandlers(
{ isEditing: false, editingId: null },
{
toggleEditing: ({ isEditing, editingId }) => entryId => ({
isEditing: !isEditing,
editingId: isEditing ? null : entryId
})
}
),
withHandlers({
handleSave: ({
isEditing,
editingId,
onEdit,
onCreate,
list
}) => values => {
console.log('handling...')
if (isEditing) {
const entry = list && list.find(entry => entry.id === editingId)
return onEdit(entry.id, values)
} else return onCreate(values)
}
})
)
export default editableRow
My DataRow:
import React from 'react'
import { Button, Checkbox, Icon, Table, Input } from 'semantic-ui-react'
import PropTypes from 'prop-types'
import editableRow from 'hoc/editableRow'
const DataRow = props =>
<Table.Row>
{
props.children
}
</Table.Row>
export default editableRow()(DataRow)
My component will receive the functions and the states I made with the HOC,
but for some reason I can't pass it anything (like calling the callbacks [onEdit, onCreate]). And isn't there a nicer way to call the handleSave instead of onSubmit={()=>props.handleSave(props1, props2, ...)}
UPDATE:
Well my problem is that I can't send any 'handler' to my component in any way. I tried it like:
<Table.Row onClick={()=>props.handleSave(
false,
false,
props.onCreate,
props.onEditing,
props.list
)}>
{
props.children
}
</Table.Row>
But my HOC's handleSave is just using it's own default values. I can't reach them, so I can't pass any handler to it.
My guess is that I making a very basic error somewhere, but don't know where :D
[Like when I save the field. That why I got those onEditing, onCreating event, BUT I even if I pass them my HOC is just using its OWN DEFAULTs instead of the parameters I passed to it]
HELP me guys please to understand how these are working... :D
import React from 'react'
import {compose} from 'recompose';
import { Button, Checkbox, Icon, Table, Input } from 'semantic-ui-react'
import PropTypes from 'prop-types'
import editableRow from 'hoc/editableRow'
const DataRow = props => {
const values = {
isEditing: false,
editingId: false,
onEdit: props.onCreate,
onCreate: props.onEditing,
list: props.list,
};
return (
<Table.Row onClick={() => {props.handleSave(values)}}>
{props.children}
</Table.Row>
);
}
export default compose(editableRow)(DataRow);
Whenever you'll compose your component with HOC then your HOC will have the props which you provided to this component as you're exporting the composed component.
So, in your HOC, you can access the props passed like this:
import { withStateHandlers, withHandlers, compose } from 'recompose'
const editableRow = () =>
compose(
withStateHandlers(
{ isEditing: false, editingId: null },
{
toggleEditing: ({ isEditing, editingId }) => entryId => ({
isEditing: !isEditing,
editingId: isEditing ? null : entryId
}),
handleSave: (state, props) => values => {
console.log('handling...')
if (isEditing) {
const list = values.list;
const entry = list && list.find(entry => entry.id === editingId)
return props.onEdit(entry.id, values)
}
return props.onCreate(values)
}
}
),
)
export default editableRow;
You don't have to use withHandlers explicitly when you're using withStateHandler which can be used for both state and handlers. I hope this helps, let me know if you're still stuck.
withStateHandler(arg1: an object or a function to set initial state, {
callback: (state, props) => callbackValues => {
//call any callback on props
props.handleSave(); // or props.onCreate() etc.
//return desired state from here
}
})

Categories

Resources