how to call the api initally to save the post - javascript

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)
}`

Related

React/Redux store state doesn't save JSON data?

I am using Redux to create a search bar and React to render out JSON data into a card. I am using thunk middleware and pass an initial state to React. When I make a search, I can successfully fetch data from my API, and a card is created. However, the data in the card is empty, which suggests the API data is not being stored in the properly.
Store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import {composeWithDevTools} from 'redux-devtools-extension';
import searchReducer from './reducers/searchreducers';
const store = createStore(searchReducer, composeWithDevTools(applyMiddleware(thunk)));
export default store;
searchreducers.js
const initState = {
brand: "",
result: "",
loading: false
};
const searchReducer = (state=initState, action) => {
switch (action.type) {
case "LOADING":
return {
...state,
brand: action.payload,
loading: true
};
case "LOAD_RESULT":
return {
...state,
brand: action.payload,
loading: false,
error: false
};
case "SET_ERROR":
return {
...state,
error: action.payload,
loading: false,
};
default:
return state;
}
}
export default searchReducer;
actions.js
import axios from 'axios';
export const loading = (brand) => {
return {
type: "LOADING",
payload: brand
}
}
export const loadResult = (result) => {
return {
type: "LOAD_RESULT",
payload: result
}
}
export const getResult = (searchTerm) => {
return async (dispatch) => {
dispatch(loading(searchTerm))
try {
const { data } = await axios.get(
`http://makeup-api.herokuapp.com/api/v1/products.json?brand=${searchTerm}`
)
console.log(data[0]) //returns data correctly
dispatch(loadResult(data[0]))
} catch (err) {
console.error(err)
dispatch({
type: "SET_ERROR",
payload: err
})
}
}
}
Result.js
render card
import React from 'react';
const Result = (result) => {
return(
<div className="card">
<img src={result.image_link} alt={result.name}/>
<h1>{result.brand}</h1>
<h3>{result.name}</h3>
<p>{result.description}</p>
</div>
)
}
export default Result
page.js
const Search = () => {
const result = useSelector(state => state.result)
const loading = useSelector(state => state.loading)
const dispatch = useDispatch();
useEffect(console.log(result)) //returns empty
const renderResult = () => {
return loading ? <p>Loading...</p> : <Result result={result}/>
}
const search = searchTerm => dispatch(getResult(searchTerm))
return(
<>
<div id="search">
<SearchForm getResult={search}/>
<h1>Result</h1>
{renderResult()}
</div>
</>
)
};
export default Search;
My first theory is that it's because the data is in json form but when I JSON.parse(data), it some up as [object Object] is not a valid JSON.
Also changed const Result = (result) => {...} to const Result = ({result}) => {..} but keys come up as undefined (may also be due to data format).

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

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);}

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.

Post data from form with Redux

Another newbie propblem. I want to post a post with my form. I have Post.js that looks like this:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import PostForm from './PostFormContainer';
export class Post extends Component {
static propTypes = {
posts: PropTypes.any,
fetchPosts: PropTypes.func,
sendPostData: PropTypes.func,
};
componentDidMount() {
const { fetchPosts } = this.props;
fetchPosts();
}
// onSubmit = (e, id, title, body) => {
// e.preventDefault();
// axios
// .post('https://jsonplaceholder.typicode.com/posts', {
// id,
// title,
// body,
// })
// .then(res =>
// this.setState({
// posts: [...this.state.posts, res.data],
// })
// );
// };
// onSubmit(e, id, title, body) {
// e.preventDefault();
// console.log('data');
// console.log('data', id);
// console.log('data', title);
// console.log('data', body);
// this.props.sendPostData(id, title, body);
// console.log('sendPostData', this.props.sendPostData(id, title, body));
// }
render() {
console.log('props', this.props);
const { posts } = this.props;
if (!posts.length) {
return (
<div>
<PostForm addPost={this.onSubmit} />
</div>
);
} else {
return (
<div>
<PostForm addPost={this.onSubmit} />
<br />
<div>
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
;
</div>
</div>
);
}
}
}
export default Post;
Where I have <PostForm addPost={this.onSubmit} />
My PostForm.js looks like this:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class PostForm extends Component {
///state = {
// title: '',
// body: '',
//};
static propTypes = {
posts: PropTypes.any,
// fetchPosts: PropTypes.func,
sendPostData: PropTypes.func,
};
//onChange = e => {
//this.setState({
// e.target.name zawsze będzie targetował pole z value i zmieniał jego stan
// [e.target.name]: e.target.value,
// });
//};
// onSubmit(e, id, title, body) {
// e.preventDefault();
// console.log('data');
// console.log('data', id);
// console.log('data', title);
// console.log('data', body);
// }
onSubmit(e, id, title, body) {
e.preventDefault();
console.log('data');
console.log('data', id);
console.log('data', title);
console.log('data', body);
// const post = {
// title,
// body,
// };
this.props.sendPostData(title, body);
// console.log('sendPostData', this.props.sendPostData(post));
}
render() {
console.log('props form', this.props);
const { title, body } = this.props;
return (
<div>
<h1> Add Post </h1>
<form onSubmit={e => this.onSubmit(e, title, body)}>
<div>
<label>Title: </label>
<input
type='text'
name='title'
value={title}
onChange={this.onChange}
/>
</div>
<div>
<label>Body: </label>
<textarea name='body' value={body} onChange={this.onChange} />
</div>
<button type='submit'>Submit</button>
</form>
</div>
);
}
}
export default PostForm;
Here I want to send this with my action.
I have two container files
PostFormContainer.js
import { connect } from 'react-redux';
import PostForm from './PostForm';
import { sendPost } from '../reducers/postReducers';
const mapStateToProps = state => ({
posts: state.posts,
});
const mapDispatchToProps = dispatch => ({
sendPostData: post => dispatch(sendPost(post)),
});
export default connect(mapStateToProps, mapDispatchToProps)(PostForm);
and PostContainer.js
import { connect } from 'react-redux';
import Post from './Post';
import { fetchFromApi } from '../reducers/postReducers';
const mapStateToProps = state => ({
posts: state.posts,
});
const mapDispatchToProps = dispatch => ({
fetchPosts: () => dispatch(fetchFromApi()),
// sendPostData: (id, title, body) => dispatch(sendPost({ id, title, body })),
});
export default connect(mapStateToProps, mapDispatchToProps)(Post);
and my reducer
import Axios from 'axios';
const reducerName = 'posts';
const createActionName = name => `/${reducerName}/${name}`;
/* action type */
const FETCH_POSTS = createActionName('FETCH_POSTS');
const SUBMIT_POST = createActionName('SUBMIT_POST');
/* action creator */
export const fetchStarted = payload => ({ payload, type: FETCH_POSTS });
export const submitPost = payload => ({ payload, type: SUBMIT_POST });
/* thunk */
export const fetchFromApi = () => {
return (dispatch, getState) => {
Axios.get('https://jsonplaceholder.typicode.com/posts?_limit=5').then(
res => dispatch(fetchStarted(res.data))
// console.log('res', res)
// console.log('res data', res.data)
);
};
};
export const sendPost = (postId, postTitle, postBody) => {
return (dispatch, getState) => {
Axios.post('https://jsonplaceholder.typicode.com/posts', {
id: postId,
title: postTitle,
body: postBody,
}).then(res => {
dispatch(submitPost(res.data));
});
};
};
/* reducer */
export default function reducer(state = [], action = {}) {
switch (action.type) {
case FETCH_POSTS:
return action.payload;
case SUBMIT_POST: {
return {
...state,
data: action.payload,
};
}
default:
return state;
}
}
Right now my console.logs shows that all my data is undefined. Not sure what the I am missing, but I can't solve this.
Here is also my stro.js
import { combineReducers, applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import postReducer from './reducers/postReducers';
const initialState = {
posts: {
data: {},
},
};
const reducers = {
posts: postReducer,
};
Object.keys(initialState).forEach(item => {
if (typeof reducers[item] == 'undefined') {
reducers[item] = (state = null) => state;
}
});
const combinedReducers = combineReducers(reducers);
const store = createStore(
combinedReducers,
initialState,
composeWithDevTools(applyMiddleware(thunk))
);
export default store;
Your PostForm element uses props title and body, but the place where you use PostForm doesn't send it a body or title prop.
I don't know your particular use case, but in React/Redux there are two ways to send a property to an element:
<PostForm body={this.state.postFormBody} title={this.state.postFormTitle} />
Or by using your Redux connector, mapStateToProps function, and returning an object with 'body' and 'title' keys that match something in your Redux store

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.

Categories

Resources