Refactoring React class to hooks - Entity update component - javascript

I have this React component that I use to update business entities. It basically fetches by ID on componentDidMount and sends a put request when the form is submitted. I would like to refactor this to a hook based component.
Here is the code before
import React from "react";
import axios from "axios";
//Api Helper Methods
const API_HOST = "https://api.example.com";
const get = (endPoint) =>
axios
.get(`${API_HOST}/${endPoint}`)
.then((response) => response.data);
export const put = (endPoint, payload, id) =>
axios
.put(`${API_HOST}/${endPoint}/${id}`, payload)
.then((response) => response.data);
//React route (uses React Router)
const END_POINT = `users`;
class Entity extends React.Component {
state = { entity: {}, fetching: true };
getEntity = async () => {
const { id } = this.props.match.params;
this.setState({ fetching: true });
const entity = await get(`${END_POINT}/${id}`);
this.setState({ entity, fetching: false });
};
onChange = (key, value) =>
this.setState({ entity: { ...this.state.entity, [key]: value } });
componentDidMount() {
this.getEntity();
}
onSubmit = async (e) => {
e.preventDefault();
let { entity } = this.state;
let { match } = this.props;
await put(END_POINT, entity, match.params.id);
};
render() {
const { entity, fetching } = this.state;
if (fetching) {
return <p>loading...</p>;
}
return (
<form onSubmit={this.onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => this.onChange("name", e.target.value)}
/>
<button type="submit">submit</button>
</form>
);
}
}
export default Entity;
This is what I have so far for the code after. Next step would be to extract custom hook.
const END_POINT = `users`;
export default function Entity({ match }) {
const [entity, setEntity] = useState({ name: "" });
const [fetching, setFetching] = useState( true );
const { id } = match.params;
const onChange = (key, value) => setEntity({ ...entity, [key]: value });
useEffect(() => {
const fetchEntity = async () => {
const entity = await get(`${END_POINT}/${id}`);
setEntity(entity);
setFetching(false);
};
fetchEntity();
}, [id]);
const onSubmit = async (e) => {
e.preventDefault();
await put(END_POINT, entity, id);
};
if (fetching) {
return <p>loading...</p>;
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => onChange("name", e.target.value)}
/>
<button type="submit">submit</button>
</form>
);
}

I haven't tested this but this should be close to what you want with a custom hook for your entity function.
import React, { useEffect, useState } from 'react';
const API_HOST = "https://api.example.com";
const END_POINT = `users`;
function useEntity(entityID) {
const [entity, setEntity] = useState({})
useEffect(() => {
(async () => {
await fetch(`${API_HOST}/${END_POINT}/${props.match.params}`)
.then(async res => await res.json())
.then(result => setEntity(result));
})();
}, [])
return entity
}
export default function Entity(props) {
const { id } = props.match;
const entity = useEntity(id);
const onSubmit = async () => await fetch(`${API_HOST}/${END_POINT}/${id}`, {method: 'PUT', body: entity})
if (!entity) {
return <p>loading...</p>;
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => setEntity({ ...entity, name: e.target.value})}
/>
<button type="submit">submit</button>
</form>
)
}

Thanks for the help Harben, I got it working like this.
import React, {useEffect, useState} from "react";
import axios from "axios";
//Api Helper Methods
const API_HOST = "https://api.example.com";
const get = (endPoint) =>
axios.get(`${API_HOST}/${endPoint}`).then((response) => response.data);
export const put = (endPoint, payload, id) =>
axios
.put(`${API_HOST}/${endPoint}/${id}`, payload)
.then((response) => response.data);
const END_POINT = `users`;
const useEntity = (entityId) => {
const [entity, setEntity] = useState({ name: "" });
const [fetching, setFetching] = useState(true);
useEffect(() => {
(async () => {
const entity = await get(`${END_POINT}/${entityId}`);
setEntity(entity);
setFetching(false);
})();
}, [entityId]);
return [entity, fetching, setEntity];
};
//React route (uses React Router)
export default function Entity({ match }) {
const { id } = match.params;
const [entity, fetching, setEntity] = useEntity(id);
const onChange = (key, value) => setEntity({ ...entity, [key]: value });
const onSubmit = async (e) => {
e.preventDefault();
await put(END_POINT, entity, id);
};
if (fetching) {
return <p>loading...</p>;
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => onChange("name", e.target.value)}
/>
<button type="submit">submit</button>
</form>
);
}

Related

How to fetch data instantly after post request - useEffect react

I want to know if that is the correct way i do it.
Firstly i fetch the hard coded data from my API and display it into the screen. I also have a form from which i send data and i want the axios.get method to instantly fetch the newest updated data that was sent from a form. I made a helper state that is put in to "useEffect array dependencies" and whenever that state changes its value, app reloads and fetches again.
Context file with useEffect hook:
import { createContext, useState, useEffect } from "react";
import { apiService } from "../services/api/api.service";
const Context = createContext({
footer: false,
subjectForm: false,
storedSubjects: [],
footerVisibilityHandler: () => {},
subjectFormVisibilityHandler: () => {},
});
export const ContextProvider = ({ children }) => {
const [footer, setFooter] = useState(false);
const [subjectForm, setSubjectForm] = useState(false);
const [storedSubjects, setStoredSubjects] = useState([]);
const [send, setSend] = useState(false);
useEffect(() => {
const getData = async () => {
try {
const getSubjects = await apiService.getSubjects();
const tableRow = getSubjects.data.map((subject) => {
return {
name: subject.name,
};
});
setStoredSubjects(tableRow);
} catch (err) {
console.log(err);
}
};
getData();
}, [send]);
const footerVisibilityHandler = () => {
setFooter((previousState) => !previousState);
setSubjectForm(false);
};
const subjectFormVisibilityHandler = () => {
setSubjectForm((previousState) => !previousState);
};
const context = {
footer,
subjectForm,
storedSubjects,
footerVisibilityHandler,
subjectFormVisibilityHandler,
setSend,
};
return <Context.Provider value={context}>{children}</Context.Provider>;
};
export default Context;
Form from which i send data:
import Context from "../../store/context";
import FormContainer from "../UI/FormContainer";
import { apiService } from "../../services/api/api.service";
import { useContext, useRef } from "react";
const AddSubject = () => {
const ctx = useContext(Context);
const subject = useRef("");
const sendData = (e) => {
e.preventDefault();
ctx.setSend((prevState) => !prevState);
apiService.addSubject(subject.current.value);
};
return (
<FormContainer show={ctx.subjectForm} send={sendData}>
<label htmlFor="subject">Subject Name: </label>
<input type="text" id="subject" ref={subject} />
<button type="submit">Add</button>
</FormContainer>
);
};
export default AddSubject;
Api endpoints file:
import axios from "axios";
const api = () => {
const baseUrl = "https://localhost:5001/api";
let optionAxios = {
headers: {
"Content-Type": "multipart/form-data",
},
};
return {
getSubjects: () => axios.get(`${baseUrl}/subjectcontroller/getsubjects`),
addSubject: (subjectName) =>
axios.post(
`${baseUrl}/subjectcontroller/createsubject`,
{
name: subjectName,
},
optionAxios
),
};
};
export const apiService = api();

Edit function not working for my CRUD App

I am building a simple CRUD app, and I have 90% of it working but for some reason I can't get the "edit" functionality to work. When I go to edit my team members name, nothing happens when I click "Edit Name". Worth noting, I copied the code from my "add user" component and modified were needed. Here is my code:
// EDIT USER
import React, { useState, useContext, useEffect } from 'react'
import { GlobalContext } from '../context/GlobalState'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { Form, FormGroup, Label, Input, Button } from 'reactstrap'
const EditUser = (props) => {
const [selectedUser, setSelectedUser] = useState({
id: '',
name: ''
});
const { users, editUser } = useContext(GlobalContext);
const navigate = useNavigate();
const currentUserId = useParams(props);
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find((user) => user.id === userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [currentUserId, users]);
const onSubmit = (e) => {
e.preventDefault()
editUser(selectedUser)
navigate('/');
}
const onChange = (e) => {
setSelectedUser({...selectedUser, [e.target.name]: e.target.value})
}
return (
<div className='container w-25'>
<Form className='form-control' onSubmit={onSubmit}>
<FormGroup>
<Label>Edit Name</Label>
<Input type='text' name='name' value={selectedUser.name} onChange={onChange} placeholder='Enter Name'></Input>
</FormGroup>
<Button type='submit'>Edit Name</Button>
<Link to='/' className='btn btn-danger m-2'>Back</Link>
</Form>
</div>
)
}
export default EditUser
// ADD USER
import React, { useState, useContext } from 'react'
import { GlobalContext } from '../context/GlobalState'
import { Link, useNavigate } from 'react-router-dom'
import { v4 as uuid } from 'uuid';
import { Form, FormGroup, Label, Input, Button } from 'reactstrap'
const AddUser = () => {
const [name, setName] = useState('');
const { addUser } = useContext(GlobalContext);
const navigate = useNavigate();
const onSubmit = (e) => {
e.preventDefault()
const newUser = {
id: uuid(),
name: name
}
addUser(newUser);
navigate('/');
}
const onChange = (e) => {
setName(e.target.value);
}
return (
<div className='container w-25'>
<Form className='form-control' onSubmit={onSubmit}>
<FormGroup>
<Label>Name</Label>
<Input
type='text'
name={name}
value={name}
onChange={onChange}
placeholder='Enter Name'></Input>
</FormGroup>
<Button type='submit'>Submit</Button>
<Link to='/' className='btn btn-danger m-2'>Back</Link>
</Form>
</div>
)
}
export default AddUser
// GLOBAL CONTEXT
import React, { createContext, useReducer } from 'react';
import AppReducer from './AppReducer';
const initialState = {
users: []
};
// Create Context \\
export const GlobalContext = createContext(initialState);
// Provider Component \\
export const GlobalProvider = ({children}) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
// Actions
const removeUser = (id) => {
dispatch({
type: 'REMOVE_USER',
payload: id
});
}
const addUser = (user) => {
dispatch({
type: 'ADD_USER',
payload: user
});
}
const editUser = (user) => {
dispatch({
type: "EDIT_USER",
payload: user
});
}
return(
<GlobalContext.Provider value={{
users: state.users,
removeUser,
addUser,
editUser
}}>
{children}
</GlobalContext.Provider>
)
}
// APP REDUCER
export default (state, action) => {
switch(action.type) {
case 'REMOVE_USER':
return {
users: state.users.filter(user => {
return user.id !== action.payload
})
}
case 'ADD_USER':
return {
users: [action.payload, ...state.users]
}
case 'EDIT_USER':
const updateUser = action.payload
const updateUsers = state.users.map(user => {
if(user.id === updateUser.id) {
return updateUser;
}
return user;
})
return {
users: updateUsers
}
default:
return state
}
}
Issue
The issue seems to be that the Edit component isn't accessing the route paths params object correctly, to reference the route path's userId param.
const currentUserId = useParams();
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find((user) => user.id === userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [currentUserId, users]);
Here the entire params object is named currentUserId. In the useEffect hook callback this params object is assigned to a local variable userId and then used to find a matching user object in the users array. It's comparing a specific user's id property (a string type) to the entire params object (an object type). This OFC will never be equal and selectedUser is undefined and the local selectedUser state is never initialized to the user you are trying to edit.
Solution
Either access into the params object to correctly access the specific parameter:
const currentUserId = useParams();
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find((user) => user.id === userId.userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [currentUserId, users]);
Or just destructure the userId path param directly:
const { userId } = useParams();
useEffect(() => {
const selectedUser = users.find((user) => user.id === userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [userId, users]);
Suggestion
Initialize the selectedUser state directly. Use a useEffect hook to check if there is a user to edit and issue a back navigation if there is not one to edit.
const EditUser = () => {
const navigate = useNavigate();
const { userId } = useParams();
const { users, editUser } = useGlobalContext(); // *
const [selectedUser, setSelectedUser] = useState(
users.find((user) => user.id === userId)
);
useEffect(() => {
if (!selectedUser) navigate(-1);
}, [navigate, selectedUser]);
const onSubmit = (e) => {
e.preventDefault();
editUser(selectedUser);
navigate("/");
};
const onChange = (e) => {
setSelectedUser({ ...selectedUser, [e.target.name]: e.target.value });
};
return (
<div className="container w-25">
{selectedUser && (
<Form className="form-control" onSubmit={onSubmit}>
<FormGroup>
<Label>Edit Name</Label>
<Input
type="text"
name="name"
value={selectedUser.name}
onChange={onChange}
placeholder="Enter Name"
/>
</FormGroup>
<Button type="submit">Edit Name</Button>
<Link to="/" className="btn btn-danger m-2">
Back
</Link>
</Form>
)}
</div>
);
};
* Note: This was just a custom hook created for convenience in the context code:
const useGlobalContext = () => useContext(GlobalContext);

Displaying response from API in react component

I'm trying to display the response from the API into my react component but it's not working. If I try to use it in the console, I can see the data and its value but not in the react component, it's empty when I try to show the value in a div.
Here is the code where I'm trying to display it in my react component:
const CharacterListing = () => {
const characters = useSelector(getAllCharacters);
console.log("Hello", characters);
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
<div>{value.name}</div>
})
return (
<div>
{renderCharacters}
</div>
);
};
export default CharacterListing;
This is the code for my Character Slice Component
const initialState = {
characters: {},
};
const characterSlice = createSlice({
name: 'characters',
initialState,
reducers: {
addCharacters: (state, { payload }) => {
state.characters = payload;
},
},
});
export const { addCharacters } = characterSlice.actions;
export const getAllCharacters = (state) => state.characters.characters;
export default characterSlice.reducer;
This is the code for my Home Component:
const Home = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchCharacters = async () => {
const response = await baseURL.get(`/characters`)
.catch(error => {
console.log("Error", error);
});
dispatch(addCharacters(response.data));
console.log("Success", response);
};
fetchCharacters();
}, [])
return (
<div>
Home
<CharacterListing />
</div>
);
};
export default Home;
Thank you
You forgot to return item into your map func
Try this :
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
return <div key={key}>{value.name}</div>
})

How to have changeable values in input React JS?

I was trying to set my value in the input value! but after that, I cannot write anything in the input field! I wanted to set values from the back end in value!
We are writing an admin channel to edit the article for that we need already existing article values to edit the article! What am I doing wrong! or Maybe you can suggest a better way to edit the article in the admin channel!
here is the code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router';
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
const [changedValues, setChangedValues] = useState('');
console.log('values', editValues);
console.log('changed', changedValues);
const params = useParams();
console.log(params);
const resultsId = params.id;
console.log('string', resultsId);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem('token') || ''
);
const setTokens = (data) => {
localStorage.setItem('token', JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/article/${resultsId}`
);
setEditValues(res.data);
} catch (err) {}
};
fetchData();
}, [resultsId]);
const inputValue = editValues;
const userToken = props.token;
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ''}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<input
// ref={editValues.shortDesc}
value={editValues.shortDesc}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<button type='submit'>send</button>
</form>
</div>
);
};
export default EditArticle;
your onChange handler is updating a different state property than what is being used as the value on the input (editValues vs changedValues).
Also you can pass a defaultValue to input that will get used as the default value only.
See more here https://reactjs.org/docs/uncontrolled-components.html
you can use just do it just using editValues. try this:
I just reproduced it without the api call to run the code.
import React, { useState, useEffect } from "react";
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
console.log("values", editValues);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem("token") || ""
);
const setTokens = (data) => {
localStorage.setItem("token", JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
//here get the data from api and setstate
setEditValues({ title: "title", shortDesc: "shortDesc" });
} catch (err) {}
};
fetchData();
}, []);
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ""}
onChange={(input) => setEditValues({title: input.target.value})}
type="text"
/>
<input
value={editValues.shortDesc}
onChange={(input) => setEditValues({shortDesc: input.target.value})}
type="text"
/>
<button type="submit">send</button>
</form>
</div>
);
};
export default EditArticle;

How to prevent React state update for asynchronous request on unmounted component? [duplicate]

This question already has answers here:
How to cancel a fetch on componentWillUnmount
(16 answers)
Closed 2 years ago.
I'm working on a mernstack app where I have a custom hook for API requests with useReducer's state and dispatch functions that is loaded into the context api. Usually GET request runs smoothly on page load, but every time I use the POST, PATCH, PUT, and DELETE request functions it causes a component to unmount and get this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
The error goes away whenever I refresh the page and see the changes. How to prevent React state update for asynchronous request on unmounted component?
Database Setup
const mongodb = require('mongodb');
const { MongoClient, ObjectID } = mongodb;
require('dotenv').config();
const mongourl = process.env.MONGO_URI;
const db_name = process.env.DB_NAME;
let db;
async function startConnection(cb) {
let client;
try {
client = await MongoClient.connect(mongourl, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
db = client.db(db_name);
await cb();
} catch (err) {
await cb(err);
}
}
const getDb = () => {
return db;
};
const getPrimaryKey = (_id) => {
return ObjectID(_id);
};
module.exports = { db, startConnection, getDb, getPrimaryKey };
Server:
const express = require('express');
require('dotenv').config();
const port = process.env.PORT || 8000;
const db = require('./db');
const db_col = process.env.DB_COL;
const router = express.Router();
let status;
db.startConnection((err) => {
if (err) {
status = `Unable to connect to the database ${err}`;
console.log(status);
} else {
status = 'Connected to the database';
console.log(status);
}
});
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/list', router);
router.get('/', (req, res) => {
db.getDb()
.collection(db_col)
.find({})
.toArray((err, docs) => {
if (err) {
console.log(err);
}
res.json(docs);
console.log(docs);
});
});
router.post('/', (req, res) => {
const newlist = req.body;
const { list_name, list_items } = newlist;
db.getDb()
.collection(db_col)
.insertOne({ list_name, list_items }, (err, docs) => {
if (err) {
console.log(err);
}
res.redirect('/');
console.log(docs);
});
});
router.patch('/:id', (req, res) => {
const paramID = req.params.id;
const listname = req.body.list_name;
db.getDb()
.collection(db_col)
.updateOne(
{ _id: db.getPrimaryKey(paramID) },
{ $set: { list_name: listname } },
(err, docs) => {
if (err) {
console.log(err);
}
res.redirect('/');
console.log(docs);
}
);
});
router.put('/:id', (req, res) => {
const paramID = req.params.id;
const listitems = req.body.list_items;
db.getDb()
.collection(db_col)
.updateOne(
{ _id: db.getPrimaryKey(paramID) },
{ $set: { list_items: listitems } },
(err, docs) => {
if (err) {
console.log(err);
}
res.redirect('/');
console.log(docs);
}
);
});
router.delete('/:id', (req, res) => {
const paramID = req.params.id;
db.getDb()
.collection(db_col)
.deleteOne({ _id: db.getPrimaryKey(paramID) }, (err, docs) => {
if (err) {
console.log(err);
}
res.redirect('/');
console.log(docs);
});
});
app.listen(port, console.log(`Server listening to port: ${port}`));
Actions:
import { LOADING, PROCESSING_REQUEST, HANDLING_ERROR } from './actionTypes';
const loading = () => {
return {
type: LOADING,
};
};
const processingRequest = (params) => {
return {
type: PROCESSING_REQUEST,
response: params,
};
};
const handlingError = () => {
return {
type: HANDLING_ERROR,
};
};
export { loading, processingRequest, handlingError };
Reducer:
import {
LOADING,
PROCESSING_REQUEST,
HANDLING_ERROR,
} from './actions/actionTypes';
export const initialState = {
isError: false,
isLoading: false,
data: [],
};
const listReducer = (state, { type, response }) => {
switch (type) {
case LOADING:
return {
...state,
isLoading: true,
isError: false
};
case PROCESSING_REQUEST:
return {
...state,
isLoading: false,
isError: false,
data: response,
};
case HANDLING_ERROR:
return {
...state,
isLoading: false,
isError: true
};
default:
throw new Error();
}
};
export default listReducer;
Custom Hook for API Requests:
import { useEffect, useCallback, useReducer } from 'react';
import axios from 'axios';
import listReducer, { initialState } from '../../context/reducers/reducers';
import {
loading,
processingRequest,
handlingError,
} from '../../context/reducers/actions/actionCreators';
const useApiReq = () => {
const [state, dispatch] = useReducer(listReducer, initialState);
const getRequest = useCallback(async () => {
dispatch(loading());
try {
const response = await axios.get('/list');
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
const postRequest = useCallback(async (entry) => {
dispatch(loading());
try {
const response = await axios.post('/list', entry);
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
const patchRequest = useCallback(async (id, updated_entry) => {
dispatch(loading());
try {
const response = await axios.patch(`/list/${id}`, updated_entry);
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
const putRequest = useCallback(async (id, updated_entry) => {
dispatch(loading());
try {
const response = await axios.put(`/list/${id}`, updated_entry);
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
const deleteRequest = useCallback(async (id) => {
dispatch(loading());
try {
const response = await axios.delete(`/list/${id}`);
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
return [
state,
getRequest,
postRequest,
patchRequest,
putRequest,
deleteRequest,
];
};
export default useApiReq;
Context API
import React, { createContext } from 'react';
import useApiReq from '../components/custom-hooks/useApiReq';
export const AppContext = createContext();
const AppContextProvider = (props) => {
const [
state,
getRequest,
postRequest,
patchRequest,
putRequest,
deleteRequest,
] = useApiReq();
return (
<AppContext.Provider
value={{
state,
getRequest,
postRequest,
patchRequest,
putRequest,
deleteRequest,
}}
>
{props.children}
</AppContext.Provider>
);
};
export default AppContextProvider;
App:
import React from 'react';
import AppContextProvider from './context/AppContext';
import Header from './components/header/Header';
import Main from './components/main/Main';
import './stylesheets/styles.scss';
function App() {
return (
<AppContextProvider>
<div className='App'>
<Header />
<Main />
</div>
</AppContextProvider>
);
}
export default App;
Main:
This is where the GET request happens on initial load.
import React, { useEffect, useContext } from 'react';
import { AppContext } from '../../context/AppContext';
import Sidebar from '../sidebar/Sidebar';
import ParentListItem from '../list-templates/ParentListItem';
function Main() {
const { state, getRequest } = useContext(AppContext);
const { isError, isLoading, data } = state;
useEffect(() => {
getRequest();
}, [getRequest]);
return (
<main className='App-body'>
<Sidebar />
<div className='list-area'>
{isLoading && (
<p className='empty-notif'>Loading data from the database</p>
)}
{isError && <p className='empty-notif'>Something went wrong</p>}
{data.length == 0 && <p className='empty-notif'>Database is empty</p>}
<ul className='parent-list'>
{data.map((list) => (
<ParentListItem key={list._id} {...list} />
))}
</ul>
</div>
</main>
);
}
export default Main;
Sidebar
import React, { useState } from 'react';
import Modal from 'react-modal';
import AddList from '../modals/AddList';
import DeleteList from '../modals/DeleteList';
/* Modal */
Modal.setAppElement('#root');
function Sidebar() {
const [addModalStatus, setAddModalStatus] = useState(false);
const [deleteModalStatus, setDeleteModalStatus] = useState(false);
const handleAddModal = () => {
setAddModalStatus((prevState) => !prevState);
};
const handleDeleteModal = () => {
setDeleteModalStatus((prevState) => !prevState);
};
return (
<aside className='sidebar'>
<nav className='nav'>
<button className='btn-rec' onClick={handleAddModal}>
Add
</button>
<button className='btn-rec' onClick={handleDeleteModal}>
Delete
</button>
</nav>
<Modal isOpen={addModalStatus} onRequestClose={handleAddModal}>
<header className='modal-header'>Create New List</header>
<div className='modal-body'>
<AddList exitHandler={handleAddModal} />
</div>
<footer className='modal-footer'>
<button onClick={handleAddModal} className='btn-circle'>
×
</button>
</footer>
</Modal>
<Modal isOpen={deleteModalStatus} onRequestClose={handleDeleteModal}>
<header className='modal-header'>Delete List</header>
<div className='modal-body'>
<DeleteList exitHandler={handleDeleteModal} />
</div>
<footer className='modal-footer'>
<button onClick={handleDeleteModal} className='btn-circle'>
×
</button>
</footer>
</Modal>
</aside>
);
}
export default Sidebar;
Add Modal
This is where the post request is called
import React, { useContext, useEffect, useState, useRef } from 'react';
import { AppContext } from '../../context/AppContext';
const AddList = ({ exitHandler }) => {
const { postRequest } = useContext(AppContext);
const [newList, setNewList] = useState({});
const inputRef = useRef(null);
/* On load set focus on the input */
useEffect(() => {
inputRef.current.focus();
}, []);
const handleAddList = (e) => {
e.preventDefault();
const new_list = {
list_name: inputRef.current.value,
list_items: [],
};
setNewList(new_list);
};
const handleSubmit = (e) => {
e.preventDefault();
postRequest(newList);
exitHandler();
};
return (
<form onSubmit={handleSubmit} className='generic-form'>
<input
type='text'
ref={inputRef}
placeholder='List Name'
onChange={handleAddList}
/>
<input type='submit' value='ADD' className='btn-rec' />
</form>
);
};
export default AddList;
Delete Modal
This is where the Delete Request is called.
import React, { useContext, useEffect, useState, useRef } from 'react';
import { AppContext } from '../../context/AppContext';
const DeleteList = ({ exitHandler }) => {
const { state, deleteRequest } = useContext(AppContext);
const { data } = state;
const selectRef = useRef();
const [targetListId, setTargetListId] = useState();
useEffect(() => {
selectRef.current.focus();
}, []);
useEffect(() => {
setTargetListId(data[0]._id);
}, [data]);
const handleDeleteList = (e) => {
e.preventDefault();
deleteRequest(targetListId);
exitHandler();
};
const handleChangeList = (e) => {
setTargetListId(e.target.value);
};
return (
<form onSubmit={handleDeleteList} className='generic-form'>
<label>
<select
ref={selectRef}
value={targetListId}
onChange={handleChangeList}
className='custom-select'
>
{data.map((list) => (
<option key={list._id} value={list._id}>
{list.list_name}
</option>
))}
</select>
</label>
<input type='submit' value='DELETE' className='btn-rec' />
</form>
);
};
export default DeleteList;
Parent List:
This is where the PUT, PATCH request is called
import React, { useContext, useState, useEffect, useRef } from 'react';
import { FaPen, FaCheck } from 'react-icons/fa';
import ChildListItem from './ChildListItem';
import { AppContext } from '../../context/AppContext';
import displayDate from '../../utilities/utilities';
import { v4 } from 'uuid';
function ParentListItem({ _id, list_name, list_items }) {
const { patchRequest, putRequest } = useContext(AppContext);
const [activeListItems, setActiveListItems] = useState([]);
const [completedListItems, setCompletedListItems] = useState([]);
const [listItems, setListItems] = useState({});
const [disabledInput, setDisabledInput] = useState(true);
const [title, setTitle] = useState({});
const [status, setStatus] = useState(false);
const titleRef = useRef();
const { day, date, month, year, current_time } = displayDate();
const handleCreateNewItem = (e) => {
const newItem = {
item_id: v4(),
item_name: e.target.value,
item_date_created: `${day}, ${date} of ${month} ${year} at ${current_time}`,
isComplete: false,
};
const new_list_items = [...list_items, newItem];
setListItems({ list_items: new_list_items });
};
/* Handles the edit list title button */
const toggleEdit = () => {
setDisabledInput(!disabledInput);
};
/* Handles the edit list title button */
const toggleStatus = (item_id) => {
const target = list_items.find((item) => item.item_id == item_id);
let updated_list = [...list_items];
updated_list.map((list) => {
if (list == target) {
list.isComplete = !list.isComplete;
}
});
const update = { list_items: updated_list };
putRequest(_id, update);
};
/* Handles the edit list title button */
const deleteItem = (item_id) => {
const target = list_items.find((item) => item.item_id == item_id);
let updated_list = [...list_items].filter((list) => {
if (target.isComplete == true) {
return list !== target;
}
});
const update = { list_items: updated_list };
putRequest(_id, update);
};
/* Handles the edit list tile input */
const handleTitleChange = (e) => {
const newTitle = { list_name: e.target.value };
setTitle(newTitle);
};
/* Handles the submit or dispatched of edited list tile*/
const handleUpdateTitle = (e) => {
e.preventDefault();
patchRequest(_id, title);
setDisabledInput(!disabledInput);
};
const handleSubmitItem = (e) => {
e.preventDefault();
putRequest(_id, listItems);
[e.target.name] = '';
};
useEffect(
(e) => {
if (disabledInput === false) titleRef.current.focus();
},
[disabledInput]
);
useEffect(() => {
setTitle(list_name);
}, [list_name]);
useEffect(() => {
/* On load filter the active list */
let active_list_items = list_items.filter(
(item) => item.isComplete === false
);
setActiveListItems(active_list_items);
}, [list_items]);
useEffect(() => {
/* On load filter the completed list */
let completed_list_items = list_items.filter(
(item) => item.isComplete === true
);
setCompletedListItems(completed_list_items);
}, [list_items]);
return (
<li className='parent-list-item'>
<header className='p-li-header'>
<input
type='text'
className='edit-input'
name='newlist'
ref={titleRef}
defaultValue={list_name}
onChange={handleTitleChange}
disabled={disabledInput}
/>
{disabledInput === true ? (
<button className='btn-icon' onClick={toggleEdit}>
<FaPen />
</button>
) : (
<form onSubmit={handleUpdateTitle}>
<button className='btn-icon' type='submit'>
<FaCheck />
</button>
</form>
)}
</header>
<div id={_id} className='p-li-form-container'>
<form className='generic-form clouds' onSubmit={handleSubmitItem}>
<input
type='text'
placeholder='Add Item'
name='itemname'
onChange={handleCreateNewItem}
/>
<input type='submit' value='+' className='btn-circle' />
</form>
</div>
<div
className={list_items.length === 0 ? 'p-li-area hidden' : 'p-li-area'}
>
<section className='pi-child-list-container'>
<h6>Active: {activeListItems.length}</h6>
{activeListItems.length === 0 ? (
<p className='empty-notif'>List is empty</p>
) : (
<ul className='child-list'>
{activeListItems.map((list) => (
<ChildListItem
key={list.item_id}
{...list}
list_id={_id}
toggleStatus={toggleStatus}
deleteItem={deleteItem}
/>
))}
</ul>
)}
</section>
<section className='pi-child-list-container'>
<h6>Completed: {completedListItems.length}</h6>
{completedListItems.length === 0 ? (
<p className='empty-notif'>List is empty</p>
) : (
<ul className='child-list'>
{completedListItems.map((list) => (
<ChildListItem
key={list.item_id}
{...list}
list_id={_id}
toggleStatus={toggleStatus}
deleteItem={deleteItem}
/>
))}
</ul>
)}
</section>
</div>
</li>
);
}
export default ParentListItem;
Child List
import React from 'react';
import { IconContext } from 'react-icons';
import { FaTrashAlt, FaRegCircle, FaRegCheckCircle } from 'react-icons/fa';
function ChildListItem({
item_name,
item_id,
item_date_created,
isComplete,
toggleStatus,
deleteItem,
}) {
const handleIsComplete = (e) => {
e.preventDefault();
toggleStatus(item_id);
};
const handleDeleteItem = (e) => {
e.preventDefault();
deleteItem(item_id);
};
return (
<li className='c-li-item' key={item_id}>
<div className='c-li-details'>
<p className='item-name'>{item_name}</p>
<p className='date-details'>Date created: {item_date_created}</p>
</div>
<div className='c-li-cta'>
<label htmlFor={item_id} className='custom-checkbox-label'>
<input
type='checkbox'
id={item_id}
checked={isComplete}
onChange={handleIsComplete}
/>
<span className='btn-icon'>
<IconContext.Provider
value={{ className: 'react-icon ri-success' }}
>
{isComplete === false ? <FaRegCircle /> : <FaRegCheckCircle />}
</IconContext.Provider>
</span>
</label>
<button
className='btn-icon btn-delete'
disabled={!isComplete}
onClick={handleDeleteItem}
>
<IconContext.Provider
value={{
className:
isComplete === false
? 'react-icon ri-disabled'
: 'react-icon ri-danger',
}}
>
<FaTrashAlt />
</IconContext.Provider>
</button>
</div>
</li>
);
}
export default ChildListItem;
The warning occurred because your component received the response but it was already unmounted(stoped rendering)
To fix this you have to cancel the request after the component is unmounted like this using useEffect()'s cleanup function (by return cancel function) and axios like the example below
useEffect( () => {
const CancelToken = axios.CancelToken;
let cancel;
const callAPI = async () => {
try {
let res = await axios.post(`.....`, { cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
}) });
}
catch (err) {
console.log(err)
}
}
callAPI();
return (cancel);
}, []);
you can read more in axios docs: https://github.com/axios/axios
However, keep in mind this only solves the warning and not the reason it redirects after your post request.

Categories