how to update the url after saving a post - javascript

I'm building an editor which can save and update post. The problem I'm facing is that after saving the post for the first time, I get a snippetId from the server which I want to show in the url immediately or else my route is still http://localhost:8000/editor and if I hit save button again then it saves a duplicate copy with a different id. I want the editor url to be something like http://localhost:8000/editor/123 after saving for the first time so that when I hit the save button again then it updates the post instead of saving a duplicate copy with a different id. I was wondering how to tackle this problem? can someone help me find a solution for this problem
codesandbox
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: ""
};
this.commonChange = this.commonChange.bind(this);
}
commonChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
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, //if the url doesn't change then this also doesn't change so I get duplicate copies
snippetDescription: enteredText,
snippetTitle: title
});
};
render() {
return (
<>
<input
type="text"
id="titletext"
placeholder="Enter title here"
limit-to="64"
className="inptxt"
name="title"
onChange={this.commonChange}
/>
<button className="btn savebtn" onClick={this.performSave}>
Save Snippet
<i className="fas fa-save" />
</button>
<textarea name="enteredText" onChange={this.commonChange} />
</>
);
}
}
const mapStateToProps = state => ({
snippets: state.snippets
});
export default connect(
mapStateToProps,
{ savePost, retrievePost }
)(Editor);
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"
}
};
let snippetData = {
title: snippetTitle,
snippetDescription: snippetDescription
};
// --------------------------------------
console.log("in savePost action");
try {
if (snippetId == null) {
const res = await axios.post("/api/savesnippets", 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);
}
};

You can history.push() from react-router for this purpose like:
history.push('/editor' + id_you_get_from_ajax_call);
And use it where you are getting the ajax response, so for every success you will get a new id_you_get_from_ajax_call and it will update the route.
and create a matching route for editor like:
<Route path="editor/:id" component={ YOUR_COMPONENT } />
React-router history.push() Reference

Related

NUXT JS dynamic multiple search query parameters

Please, I am new to NUXT JS and need help.
I have a search page where the search bar is located in its header component which is shared across pages, and upon submitting, it goes to the search page and the search logic is done in the store store/search.js
The issue is that no matter what I search and the parameters;
I cannot get the values of the query to show on the browser URL like search?Q=jump&color=yellow
Since my search logic is in the store, I am confused about how to pass parameters from the URL and searching dynamically like that.
below is my code
search bar component
<input type="text" class="search-input" placeholder="Search for a location" #keypress="search">
methods: {
search(event) {
const btn = event.key;
if (btn === "Enter") {
const search_terms = event.target.value;
this.$store.dispatch("search/search", search_terms);
this.$router.push('/search');
}
}
}
store
store/search.js
export const state = () => ({
result: []
})
export const mutations = {
searchTerms(state, text) {
state.searchTerms = text
},
searchResult(state, result) {
state.result = result
}
}
export const actions = {
search(vuexContext, search_terms) {
vuexContext.commit('loadingTrue')
return this.$axios
.$get('https://help.herokuapp.com/place', { params:
{
search: 'lagos',
idealfor: ["production"],
page: 1,
limit: 12,
}
})
.then(data => {
let searchResult = data.results
vuexContext.commit('searchResult', searchResult)
})
.catch(e => {
console.log(e)
})
},
}
export const getters = {
searchResult(state) {
return state.result
},
}
i would like the search bar to show like
localhost:3000/search?search=lagos&page=1&limit=12&idealfor=["production"]
and when the link is sg[shared and also visited the desired result would show.
please how would you go about this in Nuxt and are there resources anyone can recommend that would help me with this.
you can update url's query by using $router.push to the same page with query included, it won't refresh the page and only updates the query.
for example:
this.$router.push('/search?search=lagos&page=1&limit=12&idealfor=["production"]');
you can access query values using $route.query.
if you want to show the result when the link is visited, you need to check for $route.query in mounted cycle and dispatch it to the store to get the results.
You can achieve it by creating the following action
async moveToGivenPathWithQuery({ _ }, { q = {}, color = {} }) {
// eslint-disable-next-line no-undef
await $nuxt.$router.push({ path: 'search', query: { q, color } })
},
index.vue
<template>
<div>
<button #click="moveToGivenPathWithQuery({ q: 'jump', color: 'yellow' })">
Go to search
</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions('search', ['moveToGivenPathWithQuery']),
},
}
</script>
search.vue
<template>
<div>Search page</div>
</template>
<script>
export default {
mounted() {
console.log('query received', this.$route.query)
},
}
</script>

How to make a POST request with input text as data React

I am new to react and I am trying to make a POST request using text field data, can anyone help me with how to store that input and make a request after a button is pressed.
I attempted to use useRef() which allowed me to obtain the data however I was not able to store it as a data object to then persist.
Currently my data persists, however it persists an empty object and the state is not being updated.
If anyone can help, I will really appreciate that.
Below is my App.js class
import React, { useState, useEffect, useRef, Component } from 'react';
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:8080/artists"
});
class App extends Component {
state = {
artists: [],
theArtistName: ""
}
constructor(props){
super(props);
this.getArtists()
}
//calling this method will allow artist array to be populated everytime an event occurs, e.g POST, PUT, DELETE
getArtists = async () =>{
let data = await api.get("/").then(({ data }) => data);
this.setState({artists: data}) //setting our artists to be the data we fetch
}
createArtist = async () =>{
let response = await api.post('/', {name: this.state.theArtistName})
console.log(response)
this.getArtists()
}
deleteArtist = async (id) =>{
let data = await api.delete('/${id}')
this.getArtists();
}
handleAddArtist = (event) =>{
event.preventDefault()
this.setState({
theArtistName: event.target.value
})
const data = this.state.theArtistName
console.log(data)
}
componentDidMount(){
this.createArtist()
}
render(){
// const {theArtistName} = this.state
return(
<>
<input type={Text} placeholder="Enter Artist Name" name="theArtistName"></input>
<button onClick={this.createArtist}>Add Artist</button>
{this.state.artists.map(artist => <h4 key={artist.id}>{artist.name}
<button onClick={() =>this.deleteArtist(artist.id)}>Delete artist</button></h4>)}
</>
)
}
}
export default App;
this.setState is an async function, it takes second argument as callback. This should solve your problem. i.e.
import React, { useState, useEffect, useRef, Component } from "react";
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:8080/artists",
});
class App extends Component {
constructor(props) {
super(props);
this.state = {
artists: [],
theArtistName: "",
};
}
//calling this method will allow artist array to be populated everytime an event occurs, e.g POST, PUT, DELETE
getArtists = async () => {
let data = await api.get("/").then(({ data }) => data);
this.setState({ artists: data }); //setting our artists to be the data we fetch
};
createArtist = async () => {
let response = await api.post("/", { name: this.state.theArtistName });
console.log(response);
this.getArtists();
};
deleteArtist = async (id) => {
let data = await api.delete("/${id}");
this.getArtists();
};
handleAddArtist = (event) => {
event.preventDefault();
this.setState(
{
theArtistName: event.target.value,
},
() => {
this.createArtist();
}
);
};
componentDidMount() {
this.getArtists();
}
render() {
// const {theArtistName} = this.state
return (
<>
<input
type={Text}
placeholder="Enter Artist Name"
name="theArtistName"
></input>
<button onClick={this.handleAddArtist}>Add Artist</button>
{this.state.artists.map((artist) => (
<h4 key={artist.id}>
{artist.name}
<button onClick={() => this.deleteArtist(artist.id)}>
Delete artist
</button>
</h4>
))}
</>
);
}
}
export default App;
Let me know if it helps.
because react update state asynchronously so when you are invoking handleAddArtist function which update state the event might be gone so you need to store the value from the event in variable like this :
handleAddArtist = (event) =>{
event.preventDefault()
const {value} = e.target
this.setState({
theArtistName: value
})
}
and to check state update there is a lifecycle method called componentDidUpdate for class component and useEffect for functional component.
[edit]:
call this.createArtist() in componentDidUpdate like this :
componentDidUpdate(prevProps,prevState){
if(prevState.theArtistName!==this.state.theArtistName)
this.createArtist()
}
so the createArtist will fire only when theArtistName state change.
First of all, useRef is a hook only meant for function components and not for class components. For using Refs in class components use React.createRef().
Usually, HTML input elements maintain their own state. The usual way to access the value of an input element from a React component that renders it is to control the input element's state via this component by adding an onChange listener and a value attribute to the input element:
class App extends Component{
constructor(props) {
super(props);
this.state = {artistName: ""};
this.handleArtistNameChange = this.handleArtistNameChange.bind(this);
}
handleArtistNameChange(event) {
this.setState({artistName: event.target.value});
}
render(){
return (
<input
type="text"
value={this.state.artistName}
onChange={this.handleArtistNameChange}
/>
);
}
}
Whenever the value of the input element changes the App component will rerender with the most up-to-date value of the input in its state.
Here is a working example:
You can read more on using form elements in React here.

React/Redux how to pass argument to modify API

I have been using Redux for the past two days, i'm getting to understand it more, however I encountered a problem which has stopped my progress.
I have an API which has interchangeable parameters.
e.g. api.example.com/data/{date}/.. and api.example.com/more-data/{regId}/..
My <Picker /> selects a value and that value should be passed to the URL, which calls the API and gives the selected data; in my case regionId.
The problem is changing the params without causing errors or getting CORS problem with the Api call. I also want to be able to set the regionId to have an initialState, so I can begin the request with a parameter in the url.
ReqService.js (just for async api calling)
class ReqService {
async getRequest(url) {
try {
let response = await (await fetch(url));
let responseJson = await response.json();
return responseJson;
} catch (error) {
console.error('Error: ', error);
}
}
}
export default new ReqService()
actions.js
import ReqService from '../ReqService';
export const IS_FETCHING = 'IS_FETCHING';
export const DATA_FETCHED = 'DATA_FETCHED';
export const ERROR_FETCHING_DATA = 'ERROR_FETCHING_DATA';
const BASE_URL = 'https://api.example.com/';
const DATE_TODAY = new Date().toISOString();
export const getTheData = (regionId) => {
// The regionId is the param i want to pass to the url
const url = `${BASE_URL}/${DATE_TODAY}/${regionId}`;
const request = ReqService.getRequest(url);
return dispatch => {
dispatch({ type: IS_FETCHING });
request
.then((data ) => {
dispatch({ type: DATA_FETCHED, payload: data });
})
.catch(error => {
dispatch({ type: ERROR_FETCHING_DATA, payload: error });
});
};
};
reducer.js
import { IS_FETCHING, DATA_FETCHED, ERROR_FETCHING_DATA } from '../Actions/actions';
const initialState = {
data: [],
fetching: false,
fetched: false,
error: null
};
export const myReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case IS_FETCHING:
return { ...state, fetching: true };
case DATA_FETCHED:
console.log('The Data Fetched ', action.payload);
return {
...state,
fetched: true,
fetching: false,
data: action.payload.data
};
case ERROR_FETCHING_DATA:
return { ...state, fetching: false, error: action.payload.error };
default:
return state;
}
};
The component where the param changes here:
import React, { Component } from 'react'
import {View, Text, Picker} from 'react-native'
import { connect } from '../../node_modules/react-redux';
import { getTheData } from './Actions/actions';
import { bindActionCreators } from "redux";
class FrontPage extends Component {
constructor(props){
super(props);
this.state = {
regionId:0
};
}
changeRegion = (regId) => {
this.props.getTheData(regId);
}
componentDidMount() {}
render() {
return (
<View>
<Text>Front Page</Text>
<Picker selectedValue={this.props.regionId}
onValueChange={itemValue => this.changeRegion(itemValue)}>
<Picker.Item label="One" value='1' />
<Picker.Item label="Two" value='2' />
</Picker>
</View>
)
}
}
const mapStateToProps = state => {
return {
data: state.data,
fetching: state.fetching,
error: state.error
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ getTheData }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(FrontPage);
I dont know if I am doing this correct, I looked at different examples and implemented what seems right. Any help will be great.
From what you are sharing it looks like a good implementation of React and Redux.
If you'd like the Picker component initially have a selected value, then set your state to what it should be. In your case, set the state regionId in your FrontPage component.
this.state = {
regionId: 1 // this will pre-select the first value.
};
"The problem is changing the params without causing errors or getting CORS problem with the Api call."
I'm unsure which problems you have when the params are changed. Can you elaborate or include a screenshot?
As for the CORS error message. Have a look at the article How to fix CORS problems to gain a better understanding of it and what you need to change. When getting this error the problem isn’t in the client application but in the server application. To fix it, you need to enable CORS support at the server level.
You can do this by setting the Access-Control-Allow-Origin header. e.g.
Access-Control-Allow-Origin: *
This will allow any host to access the API, even when they are on a different domain or post.

Apollo Client Write Query Not Updating UI

We are building an offline first React Native Application with Apollo Client. Currently I am trying to update the Apollo Cache directly when offline to update the UI optimistically. Since we offline we do not attempt to fire the mutation until connect is "Online" but would like the UI to reflect these changes prior to the mutation being fired while still offline. We are using the readQuery / writeQuery API functions from http://dev.apollodata.com/core/read-and-write.html#writequery-and-writefragment. and are able to view the cache being updated via Reacotron, however, the UI does not update with the result of this cache update.
const newItemQuantity = existingItemQty + 1;
const data = this.props.client.readQuery({ query: getCart, variables: { referenceNumber: this.props.activeCartId } });
data.cart.items[itemIndex].quantity = newItemQuantity;
this.props.client.writeQuery({ query: getCart, data });
If you look at the documentation examples, you will see that they use the data in an immutable way. The data attribute passed to the write query is not the same object as the one that is read. Mutating this object is unlikely to be supported by Apollo because it would not be very efficient for it to detect which attributes you modified, without doing deep copies and comparisons of data before/after.
const query = gql`
query MyTodoAppQuery {
todos {
id
text
completed
}
}
`;
const data = client.readQuery({ query });
const myNewTodo = {
id: '6',
text: 'Start using Apollo Client.',
completed: false,
};
client.writeQuery({
query,
data: {
todos: [...data.todos, myNewTodo],
},
});
So you should try the same code without mutating the data. You can use for example set of lodash/fp to help you
const data = client.readQuery({...});
const newData = set("cart.items["+itemIndex+"].quantity",newItemQuantity,data);
this.props.client.writeQuery({ ..., data: newData });
It recommend ImmerJS for more complex mutations
Just to save someones time. Using the data in an immutable way was the solution. Agree totally with this answer, but for me I did something else wrong and will show it here. I followed this tutorial and updating the cache worked fine as I finished the tutorial. So I tried to apply the knowledge in my own app, but there the update didn’t work even I did everything similar as showed in the tutorial.
Here was my approach to update the data using the state to access it in the render method:
// ... imports
export const GET_POSTS = gql`
query getPosts {
posts {
id
title
}
}
`
class PostList extends Component {
constructor(props) {
super(props)
this.state = {
posts: props.posts
}
}
render() {
const postItems = this.state.posts.map(item => <PostItem key={item.id} post={item} />)
return (
<div className="post-list">
{postItems}
</div>
)
}
}
const PostListQuery = () => {
return (
<Query query={GET_POSTS}>
{({ loading, error, data }) => {
if (loading) {
return (<div>Loading...</div>)
}
if (error) {
console.error(error)
}
return (<PostList posts={data.posts} />)
}}
</Query>
)
}
export default PostListQuery
The solution was just to access the date directly and not using the state at all. See here:
class PostList extends Component {
render() {
// use posts directly here in render to make `cache.writeQuery` work. Don't set it via state
const { posts } = this.props
const postItems = posts.map(item => <PostItem key={item.id} post={item} />)
return (
<div className="post-list">
{postItems}
</div>
)
}
}
Just for completeness here is the input I used to add a new post and update the cache:
import React, { useState, useRef } from 'react'
import gql from 'graphql-tag'
import { Mutation } from 'react-apollo'
import { GET_POSTS } from './PostList'
const ADD_POST = gql`
mutation ($post: String!) {
insert_posts(objects:{title: $post}) {
affected_rows
returning {
id
title
}
}
}
`
const PostInput = () => {
const input = useRef(null)
const [postInput, setPostInput] = useState('')
const updateCache = (cache, {data}) => {
// Fetch the posts from the cache
const existingPosts = cache.readQuery({
query: GET_POSTS
})
// Add the new post to the cache
const newPost = data.insert_posts.returning[0]
// Use writeQuery to update the cache and update ui
cache.writeQuery({
query: GET_POSTS,
data: {
posts: [
newPost, ...existingPosts.posts
]
}
})
}
const resetInput = () => {
setPostInput('')
input.current.focus()
}
return (
<Mutation mutation={ADD_POST} update={updateCache} onCompleted={resetInput}>
{(addPost, { loading, data }) => {
return (
<form onSubmit={(e) => {
e.preventDefault()
addPost({variables: { post: postInput }})
}}>
<input
value={postInput}
placeholder="Enter a new post"
disabled={loading}
ref={input}
onChange={e => (setPostInput(e.target.value))}
/>
</form>
)
}}
</Mutation>
)
}
export default PostInput

Publish results of an API call to a collection in Meteor

I'm attempting to make a simple RSS feed reader that will allow a user to enter a url in a search bar and display the results using Meteor and React. In my current set up, I have a SearchBar component with a function that makes a call to the meteor method on the server. How can I store the return of the API call in a client side collection? I've seen some examples on using publish and subscribe to do this, but haven't been able to follow. My goal is to save this data in a client side collection so I can access it from any components that will need it, and not have to render subsequent components through the SearchBar component's render method. This is how I currently have it set up:
feeds.js
import { Meteor } from 'meteor/meteor';
import { HTTP } from 'meteor/http';
import parser from 'rss-parser';
if(Meteor.isServer) {
Meteor.methods({
getFeed(url) {
this.unblock();
const feed = {
title: '',
entries: []
};
try {
console.log('trying to get url');
const response = HTTP.get(url);
parser.parseString(response.content, function(err, parsed) {
feed.title = parsed.feed.title;
parsed.feed.entries.forEach(function(entry) {
feed.entries.push(entry);
})
});
console.log(feed.title);
return feed;
} catch(e) {
return false;
}
}
});
}
SearchBar.js
import React, { Component } from 'react';
import { Tracker } from 'meteor/tracker';
import FeedList from './FeedList';
export default class SearchBar extends Component {
constructor(props) {
super(props);
this.state = {
results: null,
url: ''
}
}
onSubmit(e) {
const { url } = this.state;
e.preventDefault();
const response = Meteor.call('getFeed', url, (err, res) => {
if(!err) {
this.setState({
results:res.entries
});
console.log(this.state.results);
} else {
console.log(err.reason);
}
});
}
onChange(e) {
this.setState({
url: e.target.value
});
}
render() {
return (
<div>
<form onSubmit={this.onSubmit.bind(this)}>
<input type="text" placeholder="Enter a URL" value={this.state.url} onChange={this.onChange.bind(this)}/>
<button type="submit">Get Feed</button>
</form>
{this.state.results ? <FeedList feedItems={this.state.results}/> : <p>Load a feed</p>}
</div>
);
}
}
Don't get the feed on the server at all. Get it on the client, and save it using a local collection defined like:
let localCollection = new Mongo.Collection(null)
Regarding the comments:
A typical pattern for this is for a cron job to populate a collection that is published to the client and rendered there.
This is going to be way over-engineered for your needs, and it's commonly regarded as a canonically wrong answer.

Categories