I've been trying to create a blog like website where user can add new post, save them and edit them later. I'm making this website in reactjs and redux. I've few confusions as to how to edit the post, like how will my website know that the user has clicked on this certain post to edit. To do I've used link from react router with the id at the end of the url but I'm not sure if it's the right way to do. Also, when I open the editor page of an existing post, it should load as it was last saved, i.e both the title input and the textarea should already be loaded with text when a user clicks on an already existing posts from the homepage.
I've created a codesandbox of the website. I'm not sure why all the lines in the switch statements in the reducer file is underlined with red.
this is my home.js file where the snippets will load
import React from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import { onLoad, setEdit } from "./actions/posts";
import { connect } from "react-redux";
class Home extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
//Load all the snippets
onLoad();
}
render() {
const { snippets } = this.props;
return (
<div className="container">
<div className="row pt-5">
<div className="col-12 col-lg-6 offset-lg-3">
<h1 className="text-center">Snippets</h1>
</div>
</div>
<div className="row pt-5">
<div className="col-12 col-lg-6 offset-lg-3">
{snippets.map(snippet => {
return (
<div className="card my-3" key={snippet.snippetData.snippetId}>
<div className="card-header">{snippet.title}</div>
<div className="card-body">{snippet.snippetDescription}</div>
<div className="card-footer">
<div className="row">
<button
// onClick={() => this.handleEdit(snippet)}
className="btn btn-primary mx-3"
>
<Link to={`/editor/:${snippet.snippetData.snippetId}`}>
Edit
</Link>
</button>
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
snippets: state.snippets,
snippetData: state.snippetData
});
export default connect(
mapStateToProps,
{ onLoad, setEdit }
)(Home);
editor.js page
import React, { Component } from "react";
import { connect } from "react-redux";
import { savePost, retrievePost } from "./actions/posts";
class Editor extends Component {
state = {
title: "",
enteredText: ""
};
componentDidMount() {
//Load the snippet
retrievePost(); // will it load the snippetId too?
}
handleChange = event => {
const { value } = event.target;
this.setState({
enteredText: value
});
};
// 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.handlePost({ ...this.state })}
>
Save Snippet
<i className="fas fa-save" />
</button>
<div
contentEditable={true}
spellCheck="false"
name="enteredText"
placeholder="Enter your text here"
onChange={this.handleChange}
/>
</>
);
}
}
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";
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 {
const res = 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/${id}`);
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
First, I have fixed some problems for you:
https://codesandbox.io/s/amazing-bird-dd2mb
I did not fix the editor page, cuz I give up, it is meaningless to give you a working code while learning nothing.
I suggest you stop playing react now, you do not have enough experience to use a complex framework.
What problem your code has:
Wrongly import a commonJS module
Misuse combineReducers
Misuse html form element
Misuse js switch
Do not understand redux state correctly
Do not understand reducer fully
Do not have basic debuging skills
...
STOP WRITING CODE THAT YOU DO NOT UNDERSTAND
This project is too complex for a beginner.
I suggest you:
Implement a counter in vanilla js
Implement a todo list in vanilla js
RE-implement the counter with pure react, no redux, no react-router
RE-implement the counter with react + redux
RE-implement the counter with react + redux + thunk
RE-implement the counter with react + redux + saga
Repeat 3-6 but a todo list.
Then try to code a blog.
Related
I'm having some issues getting a component to re-render after submitting a form. I created separate files to store these custom hooks to make them as reusable as possible. Everything is functioning correctly, except I haven't figured out a way to re render a list component after posting a new submit to that list. I am using axios for fetch requests and react-final-form for my actual form. Am I not able to re-render the component because I am using useContext to "share" my data across child components? My comments are set up as nested attributes to each post, which is being handled through Rails. My comment list is rendered in it's own component, where I call on the usePost() function in the PostContext.js file. I can provide more info/context if needed.
**
Also, on a slightly different note. I am having difficulty clearing the form inputs after a successful submit. I'm using react-final-form and most the suggestions I've seen online are for class components. Is there a solution for functional components?
react/contexts/PostContext.js
import React, { useContext, useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { useAsync } from "../hooks/useAsync";
import { getPost } from "../services/post";
const Context = React.createContext();
export const usePost = () => {
return useContext(Context);
};
export const PostProvider = ({ children }) => {
const id = useParams();
const { loading, error, value: post } = useAsync(() => getPost(id.id), [
id.id,
]);
const [comments, setComments] = useState([]);
useEffect(() => {
if (post?.comments == null) return;
setComments(post.comments);
}, [post?.comments]);
return (
<Context.Provider
value={{
post: { id, ...post },
comments: comments,
}}
>
{loading ? <h1>Loading</h1> : error ? <h1>{error}</h1> : children}
</Context.Provider>
);
};
react/services/comment.js
import { makeRequest } from "./makeRequest";
export const createComment = ({ message, postId }) => {
message["post_id"] = postId;
return makeRequest("/comments", {
method: "POST",
data: message,
}).then((res) => {
if (res.error) return alert(res.error);
});
};
react/services/makeRequest.js
import axios from "axios";
const api = axios.create({
baseURL: "/api/v1",
withCredentials: true,
});
export const makeRequest = (url, options) => {
return api(url, options)
.then((res) => res.data)
.catch((err) => Promise.reject(err?.response?.data?.message ?? "Error"));
};
react/components/Comment/CommentForm.js
import React from "react";
import { Form, Field } from "react-final-form";
import { usePost } from "../../contexts/PostContext";
import { createComment } from "../../services/comment";
import { useAsyncFn } from "../../hooks/useAsync";
const CommentForm = () => {
const { post, createLocalComment } = usePost();
const { loading, error, execute: createCommentFn } = useAsyncFn(
createComment
);
const onCommentCreate = (message) => {
return createCommentFn({ message, postId: post.id });
};
const handleSubmit = (values) => {
onCommentCreate(values);
};
return (
<Form onSubmit={handleSubmit}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="comment-form-row">
<Field name="body">
{({ input }) => (
<textarea
className="comment-input"
placeholder="Your comment..."
type="text"
{...input}
/>
)}
</Field>
<button className="comment-submit-btn" type="submit">
Submit
</button>
</div>
</form>
)}
</Form>
);
};
export default CommentForm;
I'm looking to get some help as I've been stuck on this for a while. I have a MERN stack application interacting with the Github API. Users are able to search for Github users and save them to their profile on the app and once they do that they will also start following the user on Github. In the same fashion users are able to delete the users from their profile in the app and that will unfollow the same user on Github. The problem is that when I click on the delete user button, it does not update on the browser unless I refresh the page then I see that the user is deleted from the array of saved users. In the Profile.jsx component I am rendering the list of saved users and fetching the information from the database. In the SavedGithubUsersCard.jsx component I am mapping through the saved users data to render the user's information such as name. In DeleteButton.jsx I am deleting the specific saved user when I click on the button. My question is what am I doing wrong? is the prop change not being fired or is there another issue with UseEffect?
Here is the code:
import React, { useEffect, useContext } from 'react';
import swal from 'sweetalert';
import { AppContext } from '../context/AppContext';
import SavedGithubUsersCard from './SavedGithubUsersCard';
import axios from 'axios';
Profile.jsx
const Profile = () => {
const {
githubUserData, setGithubUserData
} = useContext(AppContext);
useEffect(() => {
axios.get('/api/githubdata').then((res) => {
setGithubUserData(res.data);
})
.catch((err) => {
if (err) {
swal('Error', 'Something went wrong.', 'error');
}
});
}, [setGithubUserData]);
return (
<div className="profile-saved-users">
<div className="githubUserData-cards">
<h1 className="saved-users-header">Saved users</h1>
{!githubUserData || githubUserData.length === 0 ? (
<p className="no-saved-users-text">No saved users yet :(</p>
) : (
<SavedGithubUsersCard githubUserData={githubUserData}/>
)}
</div>
</div>
);
};
export default Profile;
import React from 'react';
import { Card, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import DeleteButton from './DeleteButton';
SavedGithubUsersCard.jsx
const SavedGithubUsersCard = ({githubUserData}) => {
return (
<>
{githubUserData?.map(githubUserData => (
<Card key={githubUserData._id} id="saved-users-card">
<Card.Img
variant="top"
src={githubUserData.avatar_url}
id="saved-users-card-image"
/>
<Card.Body id="saved-users-card-information">
<Card.Title
id="saved-users-name"
style={{ textAlign: 'center' }}
>
{githubUserData.name}
</Card.Title>
<Card.Subtitle
id="saved-users-username"
className="mb-2 text-muted"
style={{ textAlign: 'center' }}
>
{githubUserData.login}
</Card.Subtitle>
<Card.Text id="saved-users-profile-url">
Profile URL: {githubUserData.html_url}
</Card.Text>
<Link
to={{ pathname: "githubUserData.html_url" }}
target="_blank"
>
<Button
id="saved-users-profile-button"
variant="outline-primary"
>
View profile
</Button>
</Link>
<DeleteButton githubUserDataId={githubUserData._id} githubUserDataLogin= {githubUserData.login} />
</Card.Body>
</Card>
))}
</>
);
};
export default SavedGithubUsersCard
DeleteButton.jsx
import React from 'react';
import { Button } from 'react-bootstrap';
import axios from 'axios';
import swal from 'sweetalert';
const DeleteButton = ({ githubUserDataLogin, githubUserDataId }) => {
const handleRemove = async () => {
try {
fetch(`https://api.github.com/user/following/${githubUserDataLogin}`, {
method: 'DELETE',
headers: {
Authorization: `token ${process.env.GITHUB_TOKEN}`
}
});
await axios({
method: 'DELETE',
url: `/api/githubdata/${githubUserDataId}`,
withCredentials: true
});
swal(
'Removed from profile!',
`You are no longer following ${githubUserDataLogin} on Github`,
'success'
);
} catch (err) {
swal('Error', 'Something went wrong.', 'error');
}
};
return (
<Button
variant="outline-danger"
style={{ marginLeft: 20 }}
onClick={handleRemove}
id="saved-users-delete-button"
>
Remove User
</Button>
);
};
export default DeleteButton;
AppContext.jsx
import React, { createContext, useState, useEffect } from 'react';
import axios from 'axios';
const AppContext = createContext();
const AppContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(false);
const [githubUserData, setGithubUserData] = useState([]);
const user = sessionStorage.getItem('user');
useEffect(() => {
// incase user refreshes and context is cleared.
if (user && !currentUser) {
axios
.get(`/api/users/me`, {
withCredentials: true
})
.then(({ data }) => {
setCurrentUser(data);
})
.catch((error) => console.error(error));
}
}, [currentUser, user]);
return (
<AppContext.Provider
value={{ currentUser, setCurrentUser, loading, setLoading, githubUserData, setGithubUserData}}
>
{children}
</AppContext.Provider>
);
};
export { AppContext, AppContextProvider };
Thank you!
From what I see of your code when you delete a user you successfully delete them in the back end but don't also update the local state.
Option 1 - Refetch the users list and update local state
const DeleteButton = ({ githubUserDataLogin, githubUserDataId }) => {
const { setGithubUserData } = useContext(AppContext);
const handleRemove = async () => {
try {
fetch(....);
await axios(....);
swal(....);
const usersDataRes = await axios.get('/api/githubdata');
setGithubUserData(usersDataRes.data);
} catch (err) {
swal('Error', 'Something went wrong.', 'error');
}
};
return (
...
);
};
Option 2 - Attempt to manually synchronize the local state
const DeleteButton = ({ githubUserDataLogin, githubUserDataId }) => {
const { setGithubUserData } = useContext(AppContext);
const handleRemove = async () => {
try {
fetch(....);
await axios(....);
swal(....);
setGithubUserData(users =>
users.filter(user => user._id !== githubUserDataId)
);
} catch (err) {
swal('Error', 'Something went wrong.', 'error');
}
};
return (
...
);
};
In the Case One:
Im doing a simple server-side pagination in rails and using react as front-end and redux as my state management. I have done all the things and the last thing remaining is to just pass the new generated url and fetch the new data. This data will be fetched in a another component which will generate and products.
as Im using redux in my case, how am I able to pass this data to my data fetch action ?
In the Case Two:
I have tried passing a parameter named url and dispatching the fetch action again with the url data i give to it. but the return is that the dispatch is not a function. Am I even able to rerun actions in action.jsx ?
action.jsx
export const handlePage = (e, { activePage }) => {
let pageNum = activePage
let pageString = pageNum.toString();
let url = "/api/v1/products/index/?page=" + pageString; ------> Use This ...
}
export const fetchProducts = (url) => { ------> In Here
return (dispatch) => {
console.log(url);
dispatch(fetchProductsRequest());
axios
.get(url)
.then((response) => {
// response.data is the products
const products = response.data.products;
dispatch(fetchProductsSuccess(products));
})
.catch((error) => {
// error.message is the error message
dispatch(fetchProductsFailure(error.message));
});
};
};
export class Paginator extends React.Component {
state = {
page: [],
pages: [],
};
componentDidMount() {
axios
.get("/api/v1/products/index", { withCredentials: true })
.then((response) => {
this.setState({
page: response.data.page,
pages: response.data.pages,
});
})
.catch((error) => {
console.log("Check Login Error", error);
});
}
render() {
return (
<div>
<Pagination count={this.state.pages} page={this.state.page} onChange={handlePage} />
</div>
);
}
}
Product.jsx
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import "../../style/frequentlyasked.scss";
import ItemOne from "../../files/Item-One.png";
// Redux
import { connect } from "react-redux";
import { loadCurrentItem, addToCart, fetchProducts } from "./action";
const Product = ({
mapProducts,
fetchProducts,
product,
addToCart,
loadCurrentItem,
}) => {
useEffect(() => {
fetchProducts(); -----> Using it Here !
}, []);
return (
<div className="card-deck d-flex justify-content-center">
{mapProducts.map((product) => (
<div className="card item-card" key={product.id} product={product}>
{/* Admin Card */}
{/* Header Image */}
<img className="card-img-top" src={ItemOne} alt="Card image cap" />
{/* Card Body */}
<div className="card-body">
<h4 className="card-title">{product.title}</h4>
<h5 className="card-title">$ {product.price}</h5>
<p className="card-text">{product.description}</p>
<button
className="btn btn-primary"
onClick={() => addToCart(product.id)}
>
+ Add To Cart
</button>
<a href="#" className="btn btn-danger">
<svg
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-heart-fill"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M8 1.314C12.438-3.248 23.534 4.735 8 15-7.534 4.736 3.562-3.248 8 1.314z"
/>
</svg>
</a>
</div>
{/* Card Footer */}
<div className="card-footer">
<small className="text-muted">Last updated 3 mins ago</small>
</div>
</div>
))}
</div>
);
};
const mapStateToProps = (state) => {
return {
mapProducts: state.shop.products,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (id) => dispatch(addToCart(id)),
loadCurrentItem: (item) => dispatch(loadCurrentItem(item)),
fetchProducts: () => dispatch(fetchProducts()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Product);
In your case:
useEffect(() => {
fetchProducts(page); -----> Using it Here !
}, [page]);
//and
fetchProducts: (page) => dispatch(fetchProducts(page)),
Note that your mapDispatchToProps could (and should) also be written in the map object notation:
const mapDispatchToProps = {
addToCart,
loadCurrentItem,
fetchProducts
}
Also note that the official recommendation is to use the react-redux hooks instead of connect and mapDispatchToProps.
So skip the whole connect stuff and in your component:
const Product = ({
product,
}) => {
const mapProducts = useSelector(state => state.shop.products)
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchProducts(page));
}, []);
Also, if you are still using connect, you have probable been following outdated tutorials. Redux has changed a lot over the last few years. Look into modern redux and the up-to-date official redux tutorials
By the way: there is a new API on the way for the official redux toolkit which can take care of all that data fetching for you. You can already try it out, at the moment as an extra package: https://rtk-query-docs.netlify.app/
I am Trying to loop through the data coming from database, but react is giving me this error
i have tried by rendering the list in seperate componet still it doesnot work!,i am beginner to react ecosystem.
fetched data look like-
and my page code is this-
import React, { useEffect, useState } from 'react';
import './account.scss';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectCurrentUser } from '../../reducer/user/user.selectors';
import Order from '../../models/order';
const AccountPage = ({ currentUser }) =>{
const[orders,setOrders]=useState([]);
useEffect(()=>{
const fetchFunc= async () =>{
try{
const response= await fetch(`https://orders-6exxx.firebaseio.com/userorders/${currentUser.id}.json`);
if(!response.ok){
throw new Error('Something Went Wrong!');
}
const resData= await response.json();
const loadedOrders=[];
for(const key in resData){
loadedOrders.push(
new Order(
key,
resData[key].cartItems,
resData[key].totalAmount,
new Date(resData[key].date)
));
}
setOrders(loadedOrders);
}catch(err){
throw err;
}
};
fetchFunc();
},[currentUser]);
console.log(orders);
return(
<div className='account'>
{orders&&orders.length ? (
<div className='account__container'>
{
orders.map(order =>(
<div className='account__containers__orders'>
<div className='account__containers__orders--date'>
{order.date}
</div>
{
order.items.map(item => (
<div className='account__containers__orders__item'>
<div>{item.name}</div>
<div>{item.quantity} x</div>
<div>{item.price}</div>
</div>
))
}
<div className='account__containers__orders--total'>
<span className='price--tag'>₹</span> {order.totalAmount}
</div>
</div>
))
}
</div>
) : (
<div className='orders-loading'>
No Orders
</div>
)}
</div>
);
};
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
export default connect(
mapStateToProps
)(AccountPage);
single data is loading but when i try to print multiple data, it gives me this error
You store new Date() inside your orders state and it is treated ot be an object. In order to render it you can convert it to a string
{order.date.toString()}
or
{order.date.toDateString()}
I'm following this tutorial: https://egghead.io/lessons/react-execute-mutations-to-an-aws-appsync-graphql-api-from-a-react-application
I have a simple todo react app hooked up to AppSync via amplify. The queries and mutations were autogenerated by Amplify.
Using the graphqlMutation helper, my query is supposed to be automatically updated after running my mutations, but it's not working. Upon refresh I do see the mutations are updating the AppSync backend, but I also expect it to update immediately with an optimistic response.
Here is the code:
import React, { Component } from "react";
import gql from "graphql-tag";
import { compose, graphql } from "react-apollo";
import { graphqlMutation } from "aws-appsync-react";
import { listTodos } from "./graphql/queries";
import { createTodo, deleteTodo } from "./graphql/mutations";
class App extends Component {
state = { todo: "" };
addTodo = async () => {
if (this.state.todo === "") {
return;
}
const response = await this.props.createTodo({
input: {
name: this.state.todo,
completed: false
}
});
this.setState({ todo: "" });
console.log("response", response);
};
deleteTodo = async id => {
const response = await this.props.deleteTodo({ input: { id } });
console.log("response", response);
};
render() {
return (
<div>
<div>
<input
onChange={e => this.setState({ todo: e.target.value })}
value={this.state.todo}
placeholder="Enter a name..."
/>
<button onClick={this.addTodo}>Add</button>
</div>
{this.props.todos.map(item => (
<div key={item.id}>
{item.name}{" "}
<button onClick={this.deleteTodo.bind(this, item.id)}>
remove
</button>
</div>
))}
</div>
);
}
}
export default compose(
graphqlMutation(gql(createTodo), gql(listTodos), "Todo"),
graphqlMutation(gql(deleteTodo), gql(listTodos), "Todo"),
graphql(gql(listTodos), {
options: {
fetchPolicy: "cache-and-network"
},
props: props => ({
todos: props.data.listTodos ? props.data.listTodos.items : []
})
})
)(App);
A repo containing the codebase is here: https://github.com/jbrown/appsync-todo
What am I doing wrong here that my query isn't updated?
Your input contains only properties name and completed. Tool graphqlMutation will add id automatically.
Code doesn't contains list query, I can guess than query requested for more data than name, completed and id.
So item will not be added to list because of missing required informations.
Solution is add all listed properties to createTodo.