How to update a post using react and redux [closed] - javascript

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I want to create a blog website where I want that the user can save their post and edit the same post later. I'm confused as to how to make the website know that I want to edit this specific post using snippetId and also want the website to know if it's a new post or if I opened an existing post to edit so that when I open a post to edit then the title and textarea is filled with the values received from the redux store.
I created a codesandbox for it.
Editor.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { savePost, retrievePost } from "./actions/posts";
class Editor extends Component {
constructor(props) {
super(props);
this.state = {
title: "", //should I assign them using snippetData.snippetTitle since if it's a new post then it'll be null anyway
enteredText: ""
};
}
componentDidMount() {
//Load the snippet
retrievePost(); // will it load the snippetId too?
}
handleChange = event => {
const { value } = event.target;
};
// Save Snippet
performSave = snippetData => {
const { enteredText, title } = this.state;
savePost(snippetData.snippetId, enteredText, title); //is it the right way to send the parameters to save the post??
};
render() {
return (
<>
<input
type="text"
id="titletext"
placeholder="Enter title here"
limit-to="64"
className="inptxt"
onChange={title => this.setState({ title })}
/>
<button
className="btn savebtn"
onClick={() => this.performSave({ ...this.state })}
>
Save Snippet
<i className="fas fa-save" />
</button>
<textarea
name="enteredText"
onChange={enteredText => this.setState({ enteredText })}
>
{}
</textarea>
</>
);
}
}
const mapStateToProps = state => ({
snippetData: state.snippetData
});
export default connect(
mapStateToProps,
{ savePost, retrievePost }
)(Editor);
actions.js file
import { SAVE_POST, UPDATE_POST, RETRIEVE_POST, HOME_LOADED } from "./types";
import axios from "axios";
export const savePost = ({
snippetId,
snippetDescription,
snippetTitle
}) => async dispatch => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
let snippetData = { snippetId, snippetDescription, snippetTitle };
try {
if (snippetId == null) {
const res = await axios.post(
"/api/save",
JSON.stringify(snippetData),
config
);
snippetData.snippetId = res.data;
dispatch({
type: SAVE_POST,
payload: snippetData
});
} else {
await axios.post("/api/update", JSON.stringify(snippetData), config);
dispatch({
type: UPDATE_POST,
payload: snippetData
});
}
} catch (err) {
console.log(err);
}
};
// Retrieve post
export const retrievePost = snippetId => async dispatch => {
try {
const res = await axios.post(`/api/snippetdata/${snippetId}`);
dispatch({
type: RETRIEVE_POST,
payload: res.data
});
} catch (err) {
console.error(err);
}
};
//Retrieve all the post
export const onLoad = () => async dispatch => {
try {
const res = await axios.post(`/api/mysnippets/`);
dispatch({
type: HOME_LOADED,
payload: res.data
});
} catch (err) {
console.error(err);
}
};
// edit a post
reducer.js
import {
SAVE_POST,
UPDATE_POST,
RETRIEVE_POST,
HOME_LOADED
} from "../actions/types";
import { initialState } from "../store";
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SAVE_POST:
return {
...state,
snippetData: payload
};
case UPDATE_POST:
return {
...state,
snippetData: payload
};
case RETRIEVE_POST:
return {
...state,
snippetData: payload
};
case HOME_LOADED:
return {
...state,
snippets: payload
};
case "SET_EDIT":
return {
...state,
snippetToEdit: action.snippet
};
default:
return state;
}
}

First of all, you dont have to this.performSave({ ...this.state })}. You already are in the class, so you can simply:
performSave = () => {
const { enteredText, title } = this.state;
savePost(snippetData.snippetId, enteredText, title);}
//is it the right way to send the parameters to save the post??
};
You are getting "Cannot read property 'snippetId' of undefined" because you never defined snippetData properly.
You can access parameters by this.props.match.params.snippetId. See react-router-url-parameters.
So the final save method should be:
performSave = () => {
const { enteredText, title } = this.state;
savePost(this.props.match.params.snippetId, enteredText, title);}

Related

TypeError: Products.find is not a function in( react/typescript)

I use redux thunk
why? find is notFunction
on console log redux given data
help pls
I have a strange problem because even data is sent inside the console and I can access the data but can not find in Redax even the initial value of the Hats object
my dependency
import React, { useEffect } from 'react';
import * as b from 'react-bootstrap';
import { useHistory } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux'
import { productsDetailActions } from '../StateManagement/Actions/productActions';
type Props = {
match: any;
params: any;
pro: any,
}
interface PropsInterface {
match: Props;
}
const SingleProduct = ({ match }: PropsInterface) => {
const dispatch = useDispatch()
console.log(match.params.id)
useEffect(() => {
dispatch(productsDetailActions(match.params.id))
}, [])
const Products = useSelector((state: any) => (state.productDetail.product))
console.log(Products)
const product = Products.find((item: any) => {
return item._id === match.params.id
})
console.log(product)
const history = useHistory()
return (
<div>
<b.Row className='my-2 '>
<b.Col>
<b.Button className='btn-danger' onClick={() => history.goBack()}>بازگشت</b.Button>
</b.Col>
</b.Row>
<b.Row>
<b.Col md={5}>
<b.Card className="shadow-sm p-3 mb-5 bg-white">
<b.Card.Body className=''>
<b.Image src={product && product?.image} fluid />
<b.Card.Text className="text-center">{product && product?.description}</b.Card.Text>
</b.Card.Body>
<b.Card.Body className="text-muted">
<h3 style={{ textAlign: 'center' }}>{product && product?.name}</h3>
</b.Card.Body>
</b.Card>
</b.Col>
<b.Col xs={5} md>
<b.Card className='shadow-sm p-3 mb-5 bg-white'>
<b.Card.Header>
<h5 style={{ textAlign: 'center' }}>{product && product?.name}</h5>
</b.Card.Header>
<b.Card.Body>
<b.ListGroup variant='flush'>
<b.ListGroup.Item>
{product && product?.description}
</b.ListGroup.Item>
<b.ListGroup.Item>
{product && product?.name}
</b.ListGroup.Item>
</b.ListGroup>
</b.Card.Body>
<b.Card.Footer>
<b.Button type='button' className='btn btn-block' >خرید محصول</b.Button>
</b.Card.Footer>
</b.Card>
</b.Col>
</b.Row>
</div>
)
}
export default SingleProduct
How can I prevent such problems from occurring? I have a similar one even on the map method
and my reducers are the following:
interface ActionInterface {
payload?: any,
type?:string
}
type state = {
state: any,
loading: boolean,
products?:any
}
const initialState = {
products: [],
loading:false,
}
export const productReducers = (state=initialState,action:ActionInterface) => {
switch (action.type) {
case 'send_req':
return { loading: true, products: [] }
case 'req_success':
return { loading: false, products: action.payload }
default:
return state
}
}
export const productDetailReducers = (state={product:{}},action:ActionInterface) => {
switch (action.type) {
case 'send_req_detail':
return { loading: true, ...state}
case 'req_success_detail':
return { loading: false, product: action.payload }
default:
return state
}
}
and these are the actions:
import axios from 'axios'
export const productsActions = () => async (dispatch: any) => {
try {
dispatch({ type: 'send_req' })
const response = await axios.get('http://localhost:8001/api/products')
dispatch({ type: 'req_success', payload: response.data})
}catch (err) {
console.log(err)
}
}
export const productsDetailActions = (id:any) => async (dispatch: any) => {
try {
dispatch({ type: 'send_req_detail' })
const response = await axios.get(`http://localhost:8001/api/products/${id}`)
dispatch({ type: 'req_success_detail', payload: response.data })
console.log(response.data)
}catch (err) {
console.log(err)
}
}
There's a chance you are using the wrong keys to grab something from your redux store.
You are seeing this error: Product.find is not a function because your Product is likely not an array.
I can affirm the above looking at your reducer function for product detail:
export const productDetailReducers = (state={product:{}},action:ActionInterface) => {
Notice that the initial state value here is: product:{}.
hence const Products = useSelector((state: any) => (state.productDetail.product)) will return {} which DOESN'T have a find method.
Review these lines:
const Products = useSelector((state: any) => (state.productDetail.product))
console.log(Products)
const product = Products.find((item: any) => {
return item._id === match.params.id
})
And maybe set product=[] here:
export const productDetailReducers = (state={ product:[] }, action: ActionInterface) => {
It'll be helpful if you could share a snippet of your store object.
Tip: you should really ever use any in TS as last resort.

how to call the api initally to save the post

I want to save my snippet to the store. However I'm facing some issues now, I only get the snippet id from the server after I have done the api call. So the first time, I need to send only the snippetTile and snippetDescription to the api call. I want the snippetId to check whether a post already exist or not and to update it. How do I omit the snippetId in the api/savesnippets api call when I create a snippet for the first time? I want only the snippetTitle and snippetDescription to be send to the api call as I will get the snippetId as the server response if everything goes well. Right now my api call's request payload looks something like this.
codesandbox
actions.js where I call the api
import { SAVE_POST, UPDATE_POST, RETRIEVE_POST, HOME_LOADED } from "./types";
import axios from "axios";
export const savePost = ({
snippetId,
snippetDescription,
snippetTitle
}) => async dispatch => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
let snippetData = { snippetId, snippetDescription, snippetTitle };
try {
if (snippetId == null) {
const res = await axios.post(
"/api/save",
JSON.stringify(snippetData),
config
);
snippetData.snippetId = res.data; //cause I only get snippetId from the server
dispatch({
type: SAVE_POST,
payload: snippetData
});
} else {
await axios.post("/api/update", JSON.stringify(snippetData), config);
dispatch({
type: UPDATE_POST,
payload: snippetData
});
}
} catch (err) {
console.log(err);
}
};
editor.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { savePost, retrievePost } from "./actions/posts";
class Editor extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
enteredText: ""
};
}
componentDidMount() {
//Load the snippet
retrievePost(this.props.match.params.snippetId);
}
// Save Snippet
performSave = snippets => {
console.log("save function clicked");
const { enteredText, title } = this.state;
this.props.savePost({
snippetId: this.props.match.params.snippetId, //this should be null when initially I'm creating a new post
snippetDescription: enteredText,
snippetTitle: title
});
};
render() {
return (
<>
<input
type="text"
id="titletext"
placeholder="Enter title here"
limit-to="64"
className="inptxt"
onChange={title => this.setState({ title })}
/>
<button className="btn savebtn" onClick={this.performSave}>
Save Snippet
<i className="fas fa-save" />
</button>
<textarea
name="enteredText"
onChange={enteredText => this.setState({ enteredText })}
/>
</>
);
}
}
const mapStateToProps = state => ({
snippets: state.snippets
});
export default connect(
mapStateToProps,
{ savePost, retrievePost }
)(Editor);
reducer.js
import {
SAVE_POST,
UPDATE_POST,
RETRIEVE_POST,
HOME_LOADED
} from "../actions/types";
import { initialState } from "../store";
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SAVE_POST:
return {
...state,
snippets: [payload, ...state.snippets] //this payload is only the snippetId right and not the actual snippet??
};
case UPDATE_POST:
const newState = state.filter(
post => post.snippetId !== payload.snippetId
);
return [...newState, payload];
case RETRIEVE_POST:
const newwState = state.filter(
post => post.snippetId !== payload.snippetId
);
return [...newwState, payload];
case HOME_LOADED:
return {
...state,
snippets: payload
};
default:
return state;
}
}
store.js
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import posts from "./reducers/posts";
export const initialState = {
snippets: [
{
snippetId: "1",
snippetTitle: "test",
snippetDescription: "test test"
},
{
snippetId: "2",
snippetTitle: "post2",
snippetDescription: "post 2 post2"
}
]
};
const store = createStore(posts, applyMiddleware(thunk));
export default store;
Update Action.js
import { SAVE_POST, UPDATE_POST, RETRIEVE_POST, HOME_LOADED } from "./types";
import axios from "axios";
export const savePost = ({
snippetId,
snippetDescription,
snippetTitle
}) => async dispatch => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
// remove snippetId here --------------------------------------
let snippetData = { snippetDescription, snippetTitle };
// --------------------------------------
try {
if (snippetId == null) {
const res = await axios.post(
"/api/save",
JSON.stringify(snippetData),
config
);
snippetData.snippetId = res.data; //cause I only get snippetId from the server
dispatch({
type: SAVE_POST,
payload: snippetData
});
} else {
//add snippetId here for update use only --------------------------------------
await axios.post("/api/update", JSON.stringify({...snippetData, snippetId}), config);
// --------------------------------------
dispatch({
type: UPDATE_POST,
payload: snippetData
});
}
} catch (err) {
console.log(err);
}
};
// consider splitting your code with functions
const addToState = (state, action) => {
const cloneSnippets = JSON.stringify(JSON.parse(state.snippets)) // for deep copy
cloneSnippets.push(action.payload) // payload contains all the snipets info (id, descrip.., )
return {
...state,
snippets : cloneSnippets
}
}
case SAVE_POST:
return addToState(state, action)
}`

How can I get step by step data from api in redux/react by infinite scroll

I want to get 20 posts by scroll down each time how can i do? my project have big Data and I use redux for get data, can I get data step by step? for example get 20 posts for first time and when a user scroll down load the next 20 posts.
I use React Hooks for develop
my posts component source is:
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import PostItem from './PostItem';
import { getPosts } from '../../actions/post';
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
return loading ? <Spinner /> : (
{posts.map(post => (
<PostItem key={post._id} post={post} />
))}
)
}
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPosts })(Posts)
my action code is:
import { setAlert } from './alert';
import {
GET_POSTS,
POST_ERROR
} from "../actions/types";
// Get Posts
export const getPosts = () => async dispatch => {
try {
const res = await axios.get('/api/ads');
dispatch({
type: GET_POSTS,
payload: res.data
});
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.satusText, status: err.response.satus }
});
}
}```
///////////////////////////////
///////////////AND REDUCER IS :
import {
GET_POSTS,
POST_ERROR
} from '../actions/types';
const initialState = {
posts: [],
loading: true,
error: {}
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_POSTS:
return {
...state,
posts: payload,
loading: false
}
case POST_ERROR:
return {
...state,
error: payload,
loading: false
}
default:
return state;
}
}
You can use react-infinite-scroller library. I've tried to change your Posts method, so maybe it would be useful.but as mentioned in comments you should add pagination to your API.
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
const itemsPerPage = 20;
const [hasMoreItems, sethasMoreItems] = useState(true);
const [records, setrecords] = useState(itemsPerPage);
const showItems=(posts)=> {
var items = [];
for (var i = 0; i < records; i++) {
items.push( <PostItem key={posts[i]._id} post={posts[i]} />);
}
return items;
}
const loadMore=()=> {
if (records === posts.length) {
sethasMoreItems(false);
} else {
setTimeout(() => {
setrecords(records + itemsPerPage);
}, 2000);
}
}
return <InfiniteScroll
loadMore={loadMore}
hasMore={hasMoreItems}
loader={<div className="loader"> Loading... </div>}
useWindow={false}
>
{showItems()}
</InfiniteScroll>{" "}
}
Working Codesandbox sample with fake data.

Timeout for RefreshView in React Native Expo App

My current React Native Expo app has a ScrollView that implements RefreshControl. A user pulling down the ScrollView will cause the onRefresh function to be executed, which in turns call an action creator getSpotPrices that queries an API using axios.
Problem: If there is a network problem, the axios.get() function will take very long to time out. Thus, there is a need to implement the timing out of either axios.get() or onRefresh.
How can we implement a timeout function into RefreshControl?
/src/containers/main.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ScrollView, RefreshControl } from 'react-native';
import MyList from '../components/MyList';
import { getSpotPrices } from '../actions';
class RefreshableList extends Component {
onRefresh = () => {
this.props.getSpotPrices();
}
render() {
return (
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.props.isLoading}
onRefresh={this._onRefresh}
/>
}>
<MyList />
</ScrollView>
)
}
}
const mapStateToProps = (state) => {
return {
isLoading: state.currencies.isLoading,
}
}
const mapDispatchToProps = (dispatch) => {
return {
getSpotPrices: () => dispatch(getSpotPrices()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(RefreshableList);
/src/actions/index.js
import api from "../utils/api";
import * as types from "../types";
import Axios from "axios";
const getSpotPrice = async () => {
try {
const res = await Axios.get(`https://api.coinbase.com/v2/prices/spot`);
return parseFloat(res.data.data.amount);
} catch (err) {
throw new Error(err);
}
};
export const getSpotPrices = () => async dispatch => {
try {
const price = await getSpotPrice();
dispatch({
type: types.CURRENCIES_SET,
payload: price
});
} catch (err) {
dispatch({
type: types.CURRENCIES_FAILED_FETCH,
payload: err.toString()
});
} finally {
dispatch({
type: types.CURRENCIES_IS_LOADING,
payload: false
})
}
};
/src/reducers/currencies.js
import * as types from "../types";
const initialState = {
data: {},
isLoading: false,
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case types.CURRENCIES_SET:
return {
...state,
data: payload,
error: "",
isLoading: false
};
case types.CURRENCIES_FAILED_FETCH:
return {
...state,
error: payload,
isLoading: false
};
case types.CURRENCIES_IS_LOADING:
return {
isLoading: payload
}
default:
return state;
}
};
Check if user is connected internet or not using the react-native-netinfo library
NetInfo.fetch().then(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
this.setState({ connected: state.isConnected });
});
// Subscribe
const unsubscribe = NetInfo.addEventListener(state => {
console.log("Connection type", state.type);
this.setState({ connected: state.isConnected });
});
// Unsubscribe
unsubscribe(); <- do this in componentwillunmount
Its generally a good practice to add a timeout, in all your api calls, in axios you can easily add a timeout option like:
await axios.get(url, { headers, timeout: 5000 })
so in your case modify the axios call as
await Axios.get(https://api.coinbase.com/v2/prices/spot, { timeout: 5000 } );
I have put timeout of 5 seconds you can modify the parameter according to your need.

POST http://localhost:3000/api/courses/[object%20Object]/units 404 (Not Found)

(Only my 3rd post here, so please excuse any blatant issues).
The following is my Unit component, a child of a Course component (courses has_many units).
import React from 'react';
import { connect } from 'react-redux';
import { getUnits, addUnit, updateUnit } from '../reducers/units';
import { Container, Header, Form } from 'semantic-ui-react';
class Units extends React.Component {
initialState = { name: ''}
state = { ...this.initialState }
componentDidUpdate(prevProps) {
const { dispatch, course } = this.props
if (prevProps.course.id !== course.id)
dispatch(getUnits(course.id))
}
handleSubmit = (e) => {
debugger
e.preventDefault()
debugger
const unit = this.state
const { dispatch } = this.props
if (unit.id) {
debugger
dispatch(updateUnit(unit))
} else {
debugger
dispatch(addUnit(unit))
this.setState({ ...this.initialState })
}
}
handleChange = (e) => {
const { name, value } = e.target
this.setState({ [name]: value })
}
units = () => {
return this.props.units.map( (unit, i) =>
<ul key={i}>
<li key={unit.id}> {unit.name}</li>
<button>Edit Module Name</button>
<button>Delete Module</button>
</ul>
)
}
render() {
const { name } = this.state
return (
<Container>
<Header as="h3" textAlign="center">Modules</Header>
{ this.units() }
<button>Add a Module</button>
<Form onSubmit={this.handleSubmit}>
<Form.Input
name="name"
placeholder="name"
value={name}
onChange={this.handleChange}
label="name"
required
/>
</Form>
</Container>
)
}
}
const mapStateToProps = (state) => {
return { units: state.units, course: state.course }
}
export default connect(mapStateToProps)(Units);
The following is its reducer:
import axios from 'axios';
import { setFlash } from './flash'
import { setHeaders } from './headers'
import { setCourse } from './course'
const GET_UNITS = 'GET_UNITS';
const ADD_UNIT = 'ADD_UNIT';
const UPDATE_UNIT = 'UPDATE_UNIT';
export const getUnits = (course) => {
return(dispatch) => {
axios.get(`/api/courses/${course}/units`)
.then( res => {
dispatch({ type: GET_UNITS, units: res.data, headers: res.headers })
})
}
}
export const addUnit = (course) => {
return (dispatch) => {
debugger
axios.post(`/api/courses/${course}/units`)
.then ( res => {
dispatch({ type: ADD_UNIT, unit: res.data })
const { headers } = res
dispatch(setHeaders(headers))
dispatch(setFlash('Unit added successfully!', 'green'))
})
.catch( (err) => dispatch(setFlash('Failed to add unit.', 'red')) )
}
}
export const updateUnit = (course) => {
return (dispatch, getState) => {
const courseState = getState().course
axios.put(`/api/courses/${course.id}/units`, { course })
.then( ({ data, headers }) => {
dispatch({ type: UPDATE_UNIT, course: data, headers })
dispatch(setCourse({...courseState, ...data}))
dispatch(setFlash('Unit has been updated', 'green'))
})
.catch( e => {
dispatch(setHeaders(e.headers))
dispatch(setFlash(e.errors, 'red'))
})
}
}
export default (state = [], action) => {
switch (action.type) {
case GET_UNITS:
return action.units;
case ADD_UNIT:
return [action.unit, ...state]
case UPDATE_UNIT:
return state.map( c => {
if ( c.id === action.unit.id )
return action.unit
return c
})
default:
return state;
}
};
Note: My reducer is working for my getUnits and rendering the units properly.
Note also: when I try to submit a new unit, it ignores all of the debuggers in my handleSubmit and the debuggers in my addUnits (in the reducer), but somehow renders the flash message of "Failed to add units".
Then the console logs the error seen in the title of this post.
I raked my routes and my post is definitely supposed to go to the route as it is.
I have tried passing in the unit and the course in various ways without any change to the error.
How can it hit the flash message without hitting any of the debuggers?
How do I fix this [object%20Object]issue?
Thanks in advance!
The variable course in the following line
axios.get(`/api/courses/${course}/units`)
is an object. When you try to convert an object to a string in JavaScript, [object Object] is the result. The space is then converted to %20 for the URL request.
I would look at the contents of the course variable. Likely, what you actually want in the URL is something inside of course. Perhaps course.id.
If you are still having issues, you'll need to explain what value should go in the URL between /courses/ and /units, and where that data exists.
You are invoking addUnit and updateUnit with a parameter that is equal to this.state in handleSubmit
const unit = this.state
addUnit(unit)
As this.state is of type object, it is string concatenated as object%20object.
getUnit works fine as the parameter passed there comes from the prop course. Check the value of state inside handleSubmit.

Categories

Resources