this is the code of the StoryCreator
import React from 'react'
import { Helmet } from 'react-helmet'
import { connect } from 'react-redux'
import { Layout, Row, Col, Form, Input, Button, Divider, message, Icon, AutoComplete } from 'antd'
import { Link } from 'react-router-dom'
import ErrorPopover from '../../components/ErrorPopover/ErrorPopover'
import { success } from '../../services/story.services'
import { changeStoryTitle, changeStoryContent , changeStoryBody, changeStoryImage, changeStoryCategoryid, sendStory } from '../../actions/story.actions'
import { fetchCategories } from '../../actions/category.actions'
import '../../vendor/Shadow/Shadow.css'
import '../../vendor/Radius/Radius.css'
import './StoryCreator.css'
const { Content } = Layout
const FormItem = Form.Item
const { TextArea } = Input;
const onload = () => {
const hide = message.loading('Cargando entrada..');
};
class StoryCreator extends React.Component {
componentWillMount() {
this.props.dispatch(fetchCategories())
}
constructor (props) {
super(props)
}
handleChange = (e) => {
if(e.target.id === 'storyTitle') {
this.props.dispatch(changeStoryTitle(e.target.value))
}
}
handleChangeContent = (e) => {
if(e.target.id === 'storyContent') {
this.props.dispatch(changeStoryContent(e.target.value))
}
}
handleChangeBody = (e) => {
if(e.target.id === 'storyBody') {
this.props.dispatch(changeStoryBody(e.target.value))
}
}
handleChangeImage = (e) => {
if(e.target.id === 'storyImage') {
this.props.dispatch(changeStoryImage(e.target.value))
}
}
handleChangeCategoryid = (e) => {
if(e.target.id === 'storyCategoryid') {
this.props.dispatch(changeStoryCategoryid(e.target.value))
}
}
handleSubmit = (e) => {
e.preventDefault()
let storyTitleVal = this.props.storyTitle
let storyContentVal = this.props.storyContent
let storyBodyVal = this.props.storyBody
let storyImageVal = this.props.storyImage
let storyCategoryidVal = this.props.storyCategoryid
if (!storyTitleVal) {
this.storyTitleInput.focus()
return
}
this.props.dispatch(sendStory(storyTitleVal, storyContentVal, storyBodyVal, storyImageVal, storyCategoryidVal), onload)
}
render () {
const {categories} = this.props;
const data = categories;
function Complete() {
return (
<FormItem style={{marginTop: '-10px'}} label='CATEGORY'>
<AutoComplete
style={{ width: 200 }}
dataSource={data}
placeholder="try to type `b`"
filterOption={(inputValue, option) => option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1}
/>
</FormItem>
);
}
return (
<div>
<Form onSubmit={this.handleSubmit}>
<FormItem label='TITLE'>
<Input id='storyTitle' value={this.props.storyTitle} onChange={this.handleChange} ref={(input) => { this.storyTitleInput = input }} size='large' />
</FormItem>
<FormItem style={{marginTop: '-10px'}} label='CONTENT'>
<Input id='storyContent' value={this.props.storyContent} onChange={this.handleChangeContent} ref={(input) => { this.storyContentInput = input }} size='large' />
</FormItem>
<FormItem style={{marginTop: '-10px'}} label='Body'>
<TextArea rows={4} id='storyBody' value={this.props.storyBody} onChange={this.handleChangeBody} ref={(input) => { this.storyBodyInput = input }} size='large' />
</FormItem>
<FormItem style={{marginTop: '-10px'}} label='IMAGE'>
<Input id='storyImage' value={this.props.storyImage} onChange={this.handleChangeImage} ref={(input) => { this.storyImageInput = input }} size='large' />
</FormItem>
<FormItem style={{marginTop: '-10px'}} label='CATEGORY'>
<Input id='storyCategoryid' value={this.props.storyCategoryid} onChange={this.handleChangeCategoryid} ref={(input) => { this.storyCategoryidInput = input }} size='large' />
</FormItem>
<Complete />
<Button onClick={onload} disabled={this.props.isBusy} style={{marginTop: '-10px'}} type='primary' size='large' htmlType='submit' className='shadow-1'>
Send
</Button>
</Form>
</div>
)
}
}
function mapStateToProps (state) {
const { isBusy } = state.appReducer
const { storyTitle, storyContent, storyBody, storyImage, storyCategoryid } = state.storyReducer
return {
isBusy,
storyTitle,
storyContent,
storyBody,
storyImage,
storyCategoryid,
categories
}
}
const StoryCreatorConnected = connect(mapStateToProps)(StoryCreator)
export default StoryCreatorConnected
and this one of the category.actions
import { CATEGORY_CHANGE_NAME, CATEGORIES_FETCHED, CATEGORY_DELETED } from "../constants/category.constants";
import { showLoading, hideLoading } from 'react-redux-loading-bar'
import { toggleBusy } from '../actions/app.actions'
import { SaveCategory, GetCategories, DeleteCategory, UpdateCategory } from '../services/category.services'
import { history } from '../helpers/history'
export const deleteCategory = (id) => {
return { type: CATEGORY_DELETED, id: id}
}
export const changeNameCategory = (name) => {
return { type: CATEGORY_CHANGE_NAME, name: name}
}
export const sendCategory = (name) => {
return dispatch => {
dispatch(toggleBusy(true))
dispatch(showLoading())
SaveCategory(name)
.then(
response => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
dispatch(changeNameCategory(''))
dispatch(fetchCategories())
},
error => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
}
)
}
}
export const updateCategory = (id, name) => {
return dispatch => {
dispatch(toggleBusy(true))
dispatch(showLoading())
dispatch(changeNameCategory(''))
history.push('/admin/category')
UpdateCategory(id, name)
.then(
response => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
dispatch(changeNameCategory(''))
dispatch(fetchCategories())
},
error => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
}
)
}
}
export const destroyCategory = (id) => {
return dispatch => {
dispatch(toggleBusy(true))
dispatch(showLoading())
DeleteCategory(id)
.then(
response => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
dispatch(fetchCategories())
},
error => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
}
)
}
}
export const fetchCategories = () => {
return dispatch => {
dispatch(toggleBusy(true))
dispatch(showLoading())
GetCategories()
.then(
response => {
console.log(response)
dispatch(toggleBusy(false))
dispatch(hideLoading())
dispatch(success(response.categories))
},
error => {
console.log(error)
dispatch(toggleBusy(false))
dispatch(hideLoading())
}
)
}
function success(categories) { return { type: CATEGORIES_FETCHED, categories: categories}}
}
what it does is communicate with a service to be able to take all the loaded categories, the problem is that I can not put all those categories in a variable to be able to list them in an Autocomplete, I do not know how to load the array into a variable.
The strange thing is that I could do it before in another component that lists all the categories. There I leave the code, I hope you can help me
import React from 'react'
import {connect} from 'react-redux'
import {
Card,
Alert,
Icon,
Button,
Table,
Divider,
Popconfirm
} from 'antd';
import {Link} from 'react-router-dom'
import {fetchCategories, destroyCategory, editCategory} from '../../actions/category.actions';
const {Meta} = Card;
class Categories extends React.Component {
componentDidMount() {
this.props.dispatch(fetchCategories())
}
handleDeleteCategory(e) {
//var removedItem = fruits.splice(pos, 1);
this.props.dispatch(destroyCategory(e.id))
}
render() {
const {categories} = this.props;
const data = categories;
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id'
}, {
title: 'Nombre de la categoria',
dataIndex: 'name',
key: 'name'
}, {
title: 'Acciones',
key: 'action',
render: (text, record) => (<span>
<Link to={`/admin/category/edit/${record.id}/${record.name}`}>Editar</Link>
<span className="ant-divider"/>
<a onClick={() => this.handleDeleteCategory(record)}>Eliminar</a>
</span>)
}
];
if (this.props.categories.length == 0)
return (<Alert message="No hay categorias para mostrar." type="error"/>);
return <Table dataSource={data} columns={columns}/>
}
}
function mapStateToProps(state) {
const {categories} = state.categoryReducer
return {categories}
}
const connectedCategories = connect(mapStateToProps)(Categories)
export default connectedCategories
solve it, forget to import the variable to the mapStateToProps and to reduce. Thank you!
Related
I would like to know how to pass the values:
onSubmit={(input, { setSubmitting, resetForm })
into a function. this is a formik code. also i would like to know how to get away with this warning:
Thank you in advance for the help!
Heres my whole code Clara:
import React, { useState } from 'react';
import { ContainerFluid } from 'strapi-helper-plugin';
import { gql } from 'apollo-boost';
import { useQuery, useMutation } from '#apollo/react-hooks';
import { StoreVariantDetails } from './StoreVariantDetails';
import { Store } from 'plugins/store-stocks/core/models/store/store';
import { FormikErrors, FormikProps, Formik, Field, Form, FormikHelpers, InjectedFormikProps, withFormik } from 'formik';
import './components-style.css';
import Modal from 'react-modal';
const STORES_GQL = gql`
{
stores {
id
name
}
}
`;
const CREATE_STORE_GQL = gql`
mutation createStore($input: createStoreInput!) {
createStore(input: $input) {
store {
id
name
}
}
}
`;
const modalStyle = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
width: 600,
height: 300,
},
};
const StoreList = () => {
const { loading, data } = useQuery<{ stores: Store[] }>(STORES_GQL, { fetchPolicy: 'network-only' });
let content: JSX.Element | JSX.Element[] = <></>;
let formContent: JSX.Element | JSX.Element[] = <></>;
const [createStore] = useMutation(CREATE_STORE_GQL);
const [showModal, setShowModal] = useState(false);
const toggleModal = () => {
setShowModal(!showModal);
};
if (loading) {
content = <p>Loading...</p>;
}
if (data) {
const { stores } = data;
if (!stores?.length) {
content = <p>No stores declared.</p>;
formContent = (
<div>
<button onClick={toggleModal} className="add-store-button">
Add New Store
</button>
<Modal isOpen={showModal} onRequestClose={toggleModal} style={modalStyle} ariaHideApp={false}>
<div>{initForm(stores, createStore)}</div>
</Modal>
</div>
);
} else {
content = stores.map(store => (
<React.Fragment key={store.id}>
<h1 key={store.id}>{store.name}</h1>
</React.Fragment>
));
formContent = (
<div>
<button onClick={toggleModal} className="add-store-button">
Add New Store
</button>
<Modal isOpen={showModal} onRequestClose={toggleModal} style={modalStyle} ariaHideApp={false}>
<div>{initForm(stores, createStore)}</div>
</Modal>
</div>
);
}
}
return (
<ContainerFluid>
<div className="store-stocks-container">
<div className="store-stocks-stores">{content}</div>
<div className="store-stocks-add-button">{formContent}</div>
</div>
</ContainerFluid>
);
};
export default StoreList;
const initForm = (stores: Store[], createStore: (data: object) => {}) => {
interface StoreFormValues {
storeName: string;
parentStore: number;
}
const initialValues: StoreFormValues = { storeName: '', parentStore: 0 };
const options = stores.map(store => (
<option key={store.id} value={store.id}>
{store.name}
</option>
));
return (
<Formik
initialValues={initialValues}
onSubmit={(input, { setSubmitting, resetForm }) => {
setSubmitting(true);
createStore({ variables: { input: { data: { name: input.storeName, parentStore: input.parentStore } } } });
alert('Store ' + input.storeName + ' has been added!');
window.location.reload();
resetForm();
setSubmitting(false);
}}
>
{({ values, isSubmitting, handleChange }) => (
<Form>
<label className="store-form-label">Name:</label>
<Field
placeholder="Store name here..."
name="storeName"
type="input"
autoComplete="off"
value={values.storeName}
onChange={handleChange}
className="store-form-input"
/>
<br />
<label className="store-form-label">ParentStore:</label>
<Field component="select" name="parentStore" value={values.parentStore} className="store-form-select">
<option value={0} />
{options}
</Field>
<button disabled={isSubmitting} type="submit" className="store-form-button">
Submit
</button>
</Form>
)}
</Formik>
);
};
I have created a component to function as a "Like/Unlike" button. When the state is true, the "Unlike" button successfully displays, but when I click "Unlike", and it DOES unlike successfully, the state should be set to false as (liked: false). However, I don't see the button.
One thing I noticed is, when I click "Unlike", the "Unlike" button disappears and the "Like" button does appear, for a millisecond, and then it vanishes in thin air. I cannot figure it out why.
Here are all the codes for my like button component:
import React from "react";
import { API, graphqlOperation } from "aws-amplify";
import { Button } from "element-react";
import { createLike, deleteLike } from "../graphql/mutations";
import { UserContext } from "../App";
class Like extends React.Component {
state = {
liked: "",
};
componentDidMount() {
this.setLiked();
}
setLiked() {
console.log(this.props);
const { user } = this.props;
const { post } = this.props;
if (post.likes.items.find((items) => items.liker === user.username)) {
this.setState({ liked: true });
console.log("liked: true");
} else {
this.setState({ liked: false });
console.log("liked: false");
}
}
handleLike = async (user) => {
try {
const input = {
liker: user.username,
likePostId: this.props.postId,
};
await API.graphql(graphqlOperation(createLike, { input }));
this.setState({
liked: true,
});
console.log("Liked!");
} catch (err) {
console.log("Failed to like", err);
}
};
handleUnlike = async (likeId) => {
try {
const input = {
id: likeId,
};
await API.graphql(graphqlOperation(deleteLike, { input }));
this.setState({
liked: false,
});
console.log("Unliked!");
} catch (err) {
console.log("Failed to unlike", err);
}
};
render() {
const { like } = this.props;
const { liked } = this.state;
return (
<UserContext.Consumer>
{({ user }) => (
<React.Fragment>
{liked ? (
<Button type="primary" onClick={() => this.handleUnlike(like.id)}>
Unlike
</Button>
) : (
<Button
type="primary"
onClick={() => this.handleLike(user, like.id)}
>
Like
</Button>
)}
</React.Fragment>
)}
</UserContext.Consumer>
);
}
}
export default Like;
The code of the parent component:
import React from "react";
import { API, graphqlOperation } from "aws-amplify";
import {
onCreateComment,
onCreateLike,
onDeleteLike,
} from "../graphql/subscriptions";
import { getPost } from "../graphql/queries";
import Comment from "../components/Comment";
import Like from "../components/Like";
import LikeButton from "../components/LikeButton";
import { Loading, Tabs, Icon } from "element-react";
import { Link } from "react-router-dom";
import { S3Image } from "aws-amplify-react";
import NewComment from "../components/NewComment";
class PostDetailPage extends React.Component {
state = {
post: null,
isLoading: true,
isAuthor: false,
};
componentDidMount() {
this.handleGetPost();
this.createCommentListener = API.graphql(
graphqlOperation(onCreateComment)
).subscribe({
next: (commentData) => {
const createdComment = commentData.value.data.onCreateComment;
const prevComments = this.state.post.comments.items.filter(
(item) => item.id !== createdComment.id
);
const updatedComments = [createdComment, ...prevComments];
const post = { ...this.state.post };
post.comments.items = updatedComments;
this.setState({ post });
},
});
this.createLikeListener = API.graphql(
graphqlOperation(onCreateLike)
).subscribe({
next: (likeData) => {
const createdLike = likeData.value.data.onCreateLike;
const prevLikes = this.state.post.likes.items.filter(
(item) => item.id !== createdLike.id
);
const updatedLikes = [createdLike, ...prevLikes];
const post = { ...this.state.post };
post.likes.items = updatedLikes;
this.setState({ post });
},
});
this.deleteLikeListener = API.graphql(
graphqlOperation(onDeleteLike)
).subscribe({
next: (likeData) => {
const deletedLike = likeData.value.data.onDeleteLike;
const updatedLikes = this.state.post.likes.items.filter(
(item) => item.id !== deletedLike.id
);
const post = { ...this.state.post };
post.likes.items = updatedLikes;
this.setState({ post });
},
});
}
componentWillUnmount() {
this.createCommentListener.unsubscribe();
}
handleGetPost = async () => {
const input = {
id: this.props.postId,
};
const result = await API.graphql(graphqlOperation(getPost, input));
console.log({ result });
this.setState({ post: result.data.getPost, isLoading: false }, () => {});
};
checkPostAuthor = () => {
const { user } = this.props;
const { post } = this.state;
if (user) {
this.setState({ isAuthor: user.username === post.author });
}
};
render() {
const { post, isLoading } = this.state;
return isLoading ? (
<Loading fullscreen={true} />
) : (
<React.Fragment>
{/*Back Button */}
<Link className="link" to="/">
Back to Home Page
</Link>
{/*Post MetaData*/}
<span className="items-center pt-2">
<h2 className="mb-mr">{post.title}</h2>
</span>
<span className="items-center pt-2">{post.content}</span>
<S3Image imgKey={post.file.key} />
<div className="items-center pt-2">
<span style={{ color: "var(--lightSquidInk)", paddingBottom: "1em" }}>
<Icon name="date" className="icon" />
{post.createdAt}
</span>
</div>
<div className="items-center pt-2">
{post.likes.items.map((like) => (
<Like
user={this.props.user}
like={like}
post={post}
postId={this.props.postId}
/>
))}
</div>
<div className="items-center pt-2">
{post.likes.items.length}people liked this.
</div>
<div>
Add Comment
<NewComment postId={this.props.postId} />
</div>
{/* Comments */}
Comments: ({post.comments.items.length})
<div className="comment-list">
{post.comments.items.map((comment) => (
<Comment comment={comment} />
))}
</div>
</React.Fragment>
);
}
}
export default PostDetailPage;
I think I know why it doesn't show up. It's because at first when the user hasn't liked it, there is no "like" object, so there is nothing to be shown, as it is only shown when there is a "like" mapped to it. I don't know how to fix it though.
I am following the serverless-stack.com tutorial. But I am stuck after creating the login button.
I keep getting the error:
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
I don't know what is causing the render so many times.
I combined my LoaderButton instead of importing to make it simpler.
import React, { useState } from "react";
import { Auth } from "aws-amplify";
import { useHistory } from "react-router-dom";
import { FormGroup, FormControl, ControlLabel } from "react-bootstrap";
import { useFormFields } from "../libs/hooksLib";
import { onError } from "../libs/errorLib";
import "../css/index.css";
const LoaderButton = (
isLoading,
className = "",
disabled = false,
...props ) => {
return(
<Button
className={`LoaderButton ${className}`}
disabled={disabled || isLoading}
{...props}
>
{isLoading && <Glyphicon glyph="refresh" className="spinning" />}
{props.children}
</Button>
)
};
export default function Login() {
let history = useHistory();
const [isLoading, setIsLoading] = useState(false);
const [fields, handleFieldChange] = useFormFields({
email: "",
password: ""
});
function validateForm() {
return fields.email.length > 0 && fields.password.length > 0;
}
async function handleSubmit(event) {
event.preventDefault();
setIsLoading(true);
try {
await Auth.signIn(fields.email, fields.password);
userHasAuthenticated(true);
console.log(history);
//history.push("/");
} catch (e) {
onError(e);
setIsLoading(false);
}
}
return (
<div className="Login">
<form onSubmit={ () => { handleSubmit() } }>
<FormGroup controlId="email" bsSize="large">
<ControlLabel>Email</ControlLabel>
<FormControl
autoFocus
type="email"
value={fields.email}
onChange={ () => { handleFieldChange() } }
/>
</FormGroup>
<FormGroup controlId="password" bsSize="large">
<ControlLabel>Password</ControlLabel>
<FormControl
type="password"
value={fields.password}
onChange={ () => { handleFieldChange() } }
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
isLoading={ () => { isLoading() } }
disabled={() => { !validateForm() }}
>
Login
</LoaderButton>
</form>
</div>
);
}
hooksLib.js / useFormFields
import { useState } from 'react'
const useFormFields = (initalState) => {
const [fields, setValues] = useState(initalState)
return [
fields,
setValues({
...fields,
[event.target.id]: event.target.value
})
]
}
export { useFormFields }
Your custom hook should look like this if you want to accept the event value:
const useFormFields = (initalState) => {
const [fields, setValues] = useState(initalState)
return [
fields,
(event) => setValues({
...fields,
[event.target.id]: event.target.value
})
]
}
Since that parameter is actually a callback that should occur.
Also, your LoadingButton implementation needs to change to this:
<LoaderButton
block
type="submit"
bsSize="large"
isLoading={isLoading} // This is a boolean value, not a function
disabled={() => !validateForm()}
>...</LoaderButton>
I am using the component from MDBreact : MDBModal
When i am importing this:
import Modal from "../Modal";
CODE:
import React from "react";
import axios from "axios";
import TableData from "../TableData";
import CustomForm from "../FormCliente";
import Modal from "../Modal";
//Función que conecta un componente a Redux store.
import { connect } from "react-redux";
import { createBrowserHistory } from 'history';
class ClienteList extends React.Component {
state = {
DataFromApi: []
};
fetchArticles = () => {
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.headers = {
"Content-Type": "application/json",
Authorization: `Token ${this.props.token}`,
};
axios.get("http://192.168.196.49:8000/clientes/api/").then(res => {
this.setState({
DataFromApi: res.data
});
});
}
componentDidMount() {
this.fetchArticles();
}
componentWillReceiveProps(newProps) {
if (newProps.token) {
this.fetchArticles();
}
}
render() {
console.log("Token_desde_connect:", this.props.token);
const history = createBrowserHistory();
const location = history.location;
console.log("debug_1.1: ", location)
const dummy = event => {
console.log('mostrando dummy:', event.target.id);
}
const encabezado = [
{
label: 'Cliente',
field: 'nombre',
sort: 'asc',
width: 150
},
{
label: 'Fecha de alta',
field: 'fecha_alta',
sort: 'asc',
width: 270
},
{
label: 'Herramientas',
field: 'usuario_id',
sort: 'asc',
width: 270
}
];
return (
<div>
<Modal requestType="post" btnText="Guardar" />
<TableData data={this.state.DataFromApi} Encabezado={encabezado}/> <br />
<h2> Create an article </h2>
<CustomForm requestType="post" itemID={null} btnText="Create" />
<button id="dummy" onClick={dummy}>Dummy button</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
token: state.token
};
};
export default connect(mapStateToProps)(ClienteList);
MODAL CODE:
import React, { Component } from 'react';
import { MDBContainer, MDBBtn, MDBModal, MDBModalBody, MDBModalHeader, MDBModalFooter } from 'mdbreact';
import { Form, Input, Button, DatePicker } from "antd";
import { connect } from "react-redux";
import axios from "axios";
import { createHashHistory } from 'history'
const FormItem = Form.Item;
class ModalPage extends Component {
state = {
modal: false
};
toggle = () => {
this.setState({
modal: !this.state.modal
});
}
handleFormSubmit = async (event, requestType, itemID) => {
event.preventDefault();
const postObj = {
fecha_alta: event.target.elements.fecha_alta.value,
nombre: event.target.elements.nombre.value,
usuario_id: event.target.elements.usuario_id.value
}
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.headers = {
"Content-Type": "application/json",
Authorization: `Token ${this.props.token}`,
};
const history = createHashHistory();
if (requestType === "post") {
await axios.post("http://192.168.196.49:8000/clientes/api/create/", postObj)
.then(res => {
if (res.status === 201) {
history.push("/proyectos/clientes/");
}
})
} else if (requestType === "put") {
await axios.put(`http://192.168.196.49:8000/clientes/api/${itemID}/update/`, postObj)
.then(res => {
if (res.status === 200) {
history.push("/proyectos/clientes/");
}
})
}
};
render() {
return (
<MDBContainer>
{/* BUTTON */}
<MDBBtn color="info" onClick={this.toggle}>Click</MDBBtn>
{/* MODAL */}
<MDBModal isOpen={this.state.modal} toggle={this.toggle} backdrop={false} >
<MDBModalHeader toggle={this.toggle}>Nuevo cliente:</MDBModalHeader>
<MDBModalBody>
<Form
onSubmit={event =>
this.handleFormSubmit(
event,
this.props.requestType,
this.props.itemID
)
}
>
<FormItem label="Nombre">
<Input name="nombre" placeholder="Nombre del cliente aquí..." />
</FormItem>
<FormItem label="ID">
<Input name="usuario_id" placeholder="ID del usuario" />
</FormItem>
<FormItem label="Fecha de Alta">
<DatePicker name="fecha_alta" />
</FormItem>
<FormItem>
<MDBBtn size="sm" color="light-blue" htmlType="submit">
{this.props.btnText}
</MDBBtn>
</FormItem>
</Form>
</MDBModalBody>
</MDBModal>
</MDBContainer>
);
}
}
const mapStateToProps = state => {
return {
token: state.token
};
};
export default connect(mapStateToProps)(ModalPage);
btnText is used to give the name to my subbit button inside the modal.
But how to cahnge the default name "Clik" before opening the modal?
I found the solution is inside the modal:
<MDBBtn size="sm" color="light-blue" onClick={this.toggle}>Nuevo</MDBBtn>
I'm building a simple campgrounds CRUD app to get some practice with the MERN stack and Redux.
Adding a campground is working fine. I'm routing to the campgrounds list page after adding a campground. But unless I reload the page, fresh data isn't retrieved.
I figured it has something to do with React's lifecycle methods.
Code:
manageCampground.js
import React, { Component } from 'react';
import TextField from '#material-ui/core/TextField';
import Card from '#material-ui/core/Card';
import Button from '#material-ui/core/Button';
import '../../styles/addCampground.css';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
actionAddCampground,
getCampgroundDetails,
actionUpdateCampground
} from './actions/campgroundActions';
class AddCampground extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
description: '',
cost: ''
};
}
componentDidMount() {
const campground = this.props.campground;
if (campground._id) {
this.props.getCampgroundDetails(campground._id);
this.setState({
name: campground.name,
description: campground.description,
cost: campground.cost
});
}
}
handleChange = e => {
const { name, value } = e.target;
this.setState({
[name]: value
});
};
addCampground = () => {
const name = this.state.name;
const description = this.state.description;
const cost = this.state.cost;
this.props.actionAddCampground({
name,
description,
cost
});
this.props.history.push('/home');
console.log('Campground added successfully');
};
updateCampground = () => {
const name = this.state.name;
const description = this.state.description;
const cost = this.state.cost;
this.props.actionUpdateCampground({
name,
description,
cost
});
this.props.history.push('/home');
console.log('Updated successfully');
};
render() {
console.log(this.props);
return (
<Card className="add-campground-card">
<TextField
name="name"
className="textfield"
label="Campground name"
variant="outlined"
value={this.state.name}
onChange={e => this.handleChange(e)}
/>
<TextField
name="description"
className="textfield"
label="Campground description"
variant="outlined"
value={this.state.description}
onChange={e => this.handleChange(e)}
/>
<TextField
name="cost"
className="textfield"
type="number"
label="Campground cost"
variant="outlined"
value={this.state.cost}
onChange={e => this.handleChange(e)}
/>
{!this.props.campground._id ? (
<Button
variant="contained"
color="primary"
onClick={this.addCampground}>
Add Campground
</Button>
) : (
<Button
variant="contained"
color="primary"
className="update-campground-btn"
onClick={this.updateCampground}>
Update Campground
</Button>
)}
</Card>
);
}
}
const mapStateToProps = state => {
return {
campground: state.campgroundList.singleCampground || ''
};
};
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
actionAddCampground,
getCampgroundDetails,
actionUpdateCampground
},
dispatch
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddCampground);
campgroundList.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getAllCampgrounds } from './actions/campgroundActions';
import Header from '../common/Header';
import Card from '#material-ui/core/Card';
import CardActionArea from '#material-ui/core/CardActionArea';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import '../../styles/landingPage.css';
class CampgroundLanding extends Component {
componentDidMount() {
this.props.getAllCampgrounds();
console.log('From component did mount');
}
render() {
const { campgrounds } = this.props;
return (
<>
<Header />
{campgrounds.map(campground => (
<Card className="campground-card" key={campground._id}>
<CardActionArea>
<CardContent>
<Typography gutterBottom variant="h5"
component="h2">
{campground.name}
</Typography>
<Typography variant="body2" color="textSecondary"
component="p">
{campground.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Link
style={{ textDecoration: 'none', color: 'white' }}
to={`/campgrounds/${campground._id}`}>
<Button size="small" color="primary">
View Details
</Button>
</Link>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
))}
<Link
style={{ textDecoration: 'none', color: 'white' }}
to="/campgrounds/add">
<Button color="primary">Add Campground</Button>
</Link>
</>
);
}
}
const mapStateToProps = state => {
return {
campgrounds: state.campgroundList.campgrounds
};
};
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
getAllCampgrounds
},
dispatch
);
};
export default connect(
mapStateToProps,
null
)(CampgroundLanding);
campgroundActions.js
import {
GET_ALL_CAMPGROUNDS,
ADD_CAMPGROUND,
GET_CAMPGROUND_DETAILS,
EDIT_CAMPGROUND
} from '../actionTypes/types';
import axios from 'axios';
const API_URL = `http://localhost:5000/api`;
export const getAllCampgrounds = () => {
return dispatch => {
axios
.get(`${API_URL}/campgrounds`)
.then(res => {
dispatch({
type: GET_ALL_CAMPGROUNDS,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const actionAddCampground = campground => {
return dispatch => {
axios
.post(`${API_URL}/campgrounds`, campground)
.then(res => {
console.log(res);
dispatch({
type: ADD_CAMPGROUND,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const getCampgroundDetails = id => {
return dispatch => {
axios
.get(`${API_URL}/campgrounds/${id}`)
.then(res => {
dispatch({
type: GET_CAMPGROUND_DETAILS,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const actionUpdateCampground = id => {
return dispatch => {
axios
.put(`${API_URL}/campgrounds/${id}`)
.then(res => {
console.log(res);
dispatch({
type: EDIT_CAMPGROUND,
payload: res
});
})
.catch(err => console.log(err));
};
};
campgroundReducers.js
import {
GET_ALL_CAMPGROUNDS,
ADD_CAMPGROUND,
GET_CAMPGROUND_DETAILS,
EDIT_CAMPGROUND
} from '../actionTypes/types';
const initialState = {
campgrounds: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_ALL_CAMPGROUNDS:
const { campgroundList } = action.payload.data;
state.campgrounds = campgroundList;
return {
...state
};
case ADD_CAMPGROUND:
const { campground } = action.payload.data;
return {
...state,
campground
};
case GET_CAMPGROUND_DETAILS:
const { singleCampground } = action.payload.data;
return { ...state, singleCampground };
case EDIT_CAMPGROUND:
const { editedCampground } = action.payload.data;
return { ...state, editedCampground };
default:
return state;
}
};
If I'm using componentDidUpdate, it's leading to infinite loop.
componentDidUpdate(prevProps) {
if (prevProps.campgrounds !== this.props.campgrounds) {
this.props.getAllCampgrounds();
}
}
I know I'm going wrong somewhere, but I can't figure out where.
You have to fix your componentDidUpdate method to avoid this infinity loop. Your are trying to compare objects via === method which will always fail according to this example:
const a = {key: 1}
const b = {key: 1}
console.log(a === b); // false
You could use for example isEqual method from lodash module to compare objects like you want https://lodash.com/docs/4.17.15#isEqual
console.log(_.isEqual({key: 1}, {key: 1})) // true
console.log({key: 1} === {key: 1}) // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Actually why do you want refresh data in this case? When you add object with success into the store this will refresh your component so you don't have to request for fresh data.