Redux action won't fire in component - reactjs

I'm trying to run an action function in a component that creates an axios request on the backend. This function (addList) works in another component, but when I try to use it in another component, it doesn't work.
Here is the component where addList doesn't work. addList gets called in the onRowAdd function.
import React, { useState } from "react";
import MaterialTable from "material-table";
import { addList } from '../../actions/profile';
import PropTypes from "prop-types";
import { connect } from "react-redux";
const ListTable = (list, {addList}) => {
const l = list;
const [data, setData] = useState(l.list.list);
console.log(l.list.list);
list = l.list.list;
var listname = l.list.name;
const handlebClick = (e) => {
e.preventDefault();
console.log({list});
};
return (
<div className="list-table">
<button onClick={e => handlebClick(e)}>Apply Sequence</button>
<MaterialTable
title={listname}
columns={[
{ title: "User Name", field: "username" },
{
title: "Link",
field: "link",
initialEditValue: ""
},
{ title: "Name", field: "name", initialEditValue: "" },
{ title: "Source", field: "source", initialEditValue: "" },
{ title: "Date Added", field: "dateAdded", type: "date" },
{ title: "Notes", field: "notes", initialEditValue: "" }
]}
data={l.list.list}
editable={{
onRowAdd: newData =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
const d = data;
d.push(newData);
setData({ d });
console.log(data);
addList(data, l.list.name, true);
resolve();
//put request to change list
}
resolve();
}, 1000);
}),
onRowUpdate: (newData, oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
const d = data;
//console.log(d, oldData);
const index = d.indexOf(oldData);
d[index] = newData;
setData({ d });
resolve();
//put request to change list
}
resolve();
}, 1000);
}),
onRowDelete: oldData =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
let d = data;
console.log(d);
if (d.length > 0) {
const index = d.indexOf(oldData);
d.splice(index, 1);
setData({ d });
}
resolve();
//put request to change list
}
resolve();
}, 1000);
})
}}
/>
</div>
);
};
ListTable.propTypes = {
addList: PropTypes.func.isRequired
};
export default connect(
null,
{ addList }
)(ListTable);
Here's the component where addList works. It's called in the submit buttons onclick.
import React, { Fragment, useState } from "react";
import { Link, withRouter } from "react-router-dom";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { addList } from "../../actions/profile";
function ListItem({ listitem, index }) {
return <div className="listitem">Username: {listitem.username}</div>;
}
function ListForm({ addListItem }) {
const [value, setValue] = useState("");
const handleSubmit = e => {
e.preventDefault();
if (!value) return;
addListItem(value);
setValue("");
};
return (
<form onSubmit={handleSubmit}>
<input
className="input"
type="text"
placeholder="* Username"
name="username"
value={value}
onChange={e => setValue(e.target.value)}
required
/>
</form>
);
}
const AddList = ({ addList }) => {
const [list, setList] = useState([]);
const addListItem = username => {
const newListItems = [...list, { username }];
setList(newListItems);
};
return (
<Fragment>
<h1 className="large text-primary">Add A List</h1>
<p className="lead">
<i className="fas fa-code-branch" /> Add a list of your followers'
usernames
</p>
<div className="form">
<div className="form-group" />
<div className="list">
{list.map((listitem, index) => (
<ListItem key={index} index={index} listitem={listitem} />
))}
<ListForm addListItem={addListItem} />
</div>
<input
type="submit"
className="btn btn-primary my-1"
onClick={e => {
e.preventDefault();
console.log(list);
const listname = prompt(
"Please enter a name for the list",
"List Name"
);
addList([{}], listname, false);
}}
/>
<Link className="btn btn-light my-1" to="/dashboard">
Go Back
</Link>
</div>
</Fragment>
);
};
AddList.propTypes = {
addList: PropTypes.func.isRequired
};
export default connect(
null,
{ addList }
)(withRouter(AddList));
The error is Uncaught TypeError: addList is not a function

Related

task.name not showing in React Firebase app

I'm working on a to-do list app in React that connects to a Firestore Database and I'm able to send the data correctly to Firebase, but my {task.name} is not displaying. The list numbers and the button are loading, but not {task.name}. The tasks are added to Firebase with the handleAdd function and the tasks are supposed to be loaded with the useEffect function. Here is the code for the main to-do.js file:
import React, { useEffect } from "react";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { db } from "./utils/firebase";
import { collection, addDoc, Timestamp, query, orderBy, onSnapshot } from "firebase/firestore";
export default function Todo() {
const [name, setName] = useState("");
const [isDoubleClicked, setDoubleClicked] = useState(false);
const [tasks, setTasks] = useState([]);
function handleChange(event) {
event.preventDefault();
setName(event.target.value);
}
const handleAdd = async (e, id) => {
e.preventDefault();
try {
await addDoc(collection(db, 'tasks'), {
name: name,
id: uuidv4(),
completed: false,
created: Timestamp.now(),
})
} catch (err) {
alert(err)
}
}
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created', 'name'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
console.log(tasks);
}, [])
function handleClick(event) {
if (event.detail === 2) {
console.log("double click");
setDoubleClicked(current => !current);
event.currentTarget.classList.toggle('double-clicked');
}
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off, click on "X" to delete an item, and drag items to reorder.</p>
</div>
<input
type="text"
value={name}
onChange={handleChange}
/>
<button
type="submit"
onClick={handleAdd}
>
Add
</button>
</div>
<ol>
{tasks.map((task => (
<li
id={task.id}
key={task.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.name} <button>x</button>
</li>
)))}
</ol>
</div>
);
};
as far as I can tell you are setting task the wrong way, instated of mapping to data stay consistent with the original uploading logic, to avoid such cases you can type your state for the sake of consistency.
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created', 'name'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
name: doc.data().name,
id: doc.data().id,
completed: doc.data().completed,
created: doc.data().created,
})))
I figured this out. I had to change {task.name} to {task.data.name}. I also updated the useEffect function. Here is the code:
import React, { useEffect } from "react";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { db } from "./utils/firebase";
import { collection, addDoc, Timestamp, query, orderBy, onSnapshot } from "firebase/firestore";
export default function Todo() {
const [name, setName] = useState("");
const [isDoubleClicked, setDoubleClicked] = useState(false);
const [tasks, setTasks] = useState([]);
function handleChange(event) {
event.preventDefault();
setName(event.target.value);
}
const handleAdd = async (e, id) => {
e.preventDefault();
try {
await addDoc(collection(db, 'tasks'), {
name: name,
id: uuidv4(),
completed: false,
created: Timestamp.now(),
})
} catch (err) {
alert(err)
}
}
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
data: doc.data()
})))
})
console.log(tasks);
}, [])
function handleClick(event) {
if (event.detail === 2) {
console.log("double click");
setDoubleClicked(current => !current);
event.currentTarget.classList.toggle('double-clicked');
}
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off, click on "X" to delete an item, and drag items to reorder.</p>
</div>
<input
type="text"
value={name}
onChange={handleChange}
/>
<button
type="submit"
onClick={handleAdd}
>
Add
</button>
</div>
<ol>
{tasks.map((task => (
<li
id={task.data.id}
key={task.data.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.data.name} <button>x</button>
</li>
)))}
</ol>
</div>
);
};

How can I display the result of the child component in the parent component?

my application consists of a filter select by provinces, the filter is a modal that shows me the result in the filter component (child), I would like to show the result in the parent not in the filter modal. I do not know what to pass to the parent to show the result or how to show it on the screen.
///parent component
import React, { useState, useEffect } from 'react'
import { useDispatch } from "react-redux";
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import { selectClinics } from '../../actions/detailClinics'
import { useNavigate } from "react-router-dom";
import contentUtils from '../../lib/contentUtils'
import { SearchFilterClinics } from './SearchFilterClinics'
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
const ClinicList = () => {
const [clinicList, setClinicList] = useState<Clinic[]>([]);
const [clinicListFiltered, setClinicListFiltered] = useState<Clinic[]>([]);
const [searchClinic, setSearchClinic] = useState("");
const dispatch = useDispatch();
const navigate = useNavigate();
///modal control
const [isOpen, setIsOpen] = useState(false);
const openModal = () => setIsOpen(true);
const closeModal = () => setIsOpen(false);
console.log(searchClinic);
const fetchClinicList = async () => {
getClinic().then((response)=>{
console.log(response)
setClinicList(response);
setClinicListFiltered(response)
}).catch ( (error) => {
console.error(error);
throw error;
});
}
const handleChange=e=>{
setSearchClinic(e.target.value);
filter(e.target.value);
}
const filter=(termSearch)=>{
const resultSearch= clinicList.filter((element)=>{
if(element.title.toString().toLowerCase().includes(termSearch.toLowerCase())
){
return element;
}
});
setClinicListFiltered(resultSearch);
}
function handleAddToDetail(clinic) {
dispatch(selectClinics(clinic));
navigate('clinicdetail');
}
function goToPageSearchFilterClinics() {
navigate('filterclinics');
}
useEffect (() => {
fetchClinicList();
}, []);
return(
<>
<div style={{display: 'flex'}}>
<div style={{width:'5rem'}}>
{/* <button onClick={() => goToPageSearchFilterClinics()}>filtro</button> */}
<button onClick={openModal}>filtro</button>
</div>
< SearchFilterClinics isOpen={isOpen} closeModal={closeModal} clinicFilter={clinicFilter}></SearchFilterClinics>
<Form className="d-flex">
<Form.Control
type="search"
value={searchClinic}
placeholder="Search"
className="me-2"
aria-label="Search"
onChange={handleChange}
/>
</Form>
</div>
<div className="content-cliniclist">
{
clinicListFiltered.map((clinic) => (
<div style={{marginBottom: '3rem'}}>
<button
type="button"
onClick={() => handleAddToDetail(clinic)}
style={{all: 'unset'}}
>
<div>
{/* <img src={ contentUtils.getLargeImageUrl(clinic.logoWidth )} alt="#"></img> */}
<div>{clinic.title}</div>
<div>{clinic.propsPhone}</div>
<div>{clinic.mobile}</div>
<div>{clinic.email}</div>
<div>{clinic.registry}</div>
</div>
</button>
</div>
))
}
</div>
</>
)
}
export default ClinicList;
////child component
import React, { useState, useEffect } from 'react'
import Select, { SingleValue } from 'react-select'
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import "./Modal.css";
interface Props {
isOpen: boolean,
clinicFilter: String,
closeModal: () => void
}
export const SearchFilterClinics : React.FC<Props> = ({ children, isOpen, closeModal, clinicFilter }) => {
////filter
type OptionType = {
value: string;
label: string;
};
const provincesList: OptionType[] = [
{ value: 'Todos', label: 'Todos' },
{ value: 'Valencia', label: 'Valencia' },
{ value: 'Castellon', label: 'Castellon' },
{ value: 'Alicante', label: 'Alicante' },
]
const [clinicList, setClinicList] = useState<Clinic[]>([]);
const [clinicListFilteredSelect, setClinicListFilteredSelect] = useState<Clinic[]>([]);
const [filterSelectClinic, setFilterSelectClinic] = useState<SingleValue<OptionType>>(provincesList[0]);
const handleChangeSelect = async (provinceList: SingleValue<OptionType>) => {
getClinic().then((response) => {
setClinicList(response);
setClinicListFilteredSelect(response)
setFilterSelectClinic(provinceList);
filterSelect(provinceList );
}).catch ((error) => {
console.error(error);
throw error;
});
}
const filterSelect=(termSearch)=>{
const resultFilterSelect = clinicList.filter((element) => {
if(element.province?.toString().toLowerCase().includes(termSearch.value.toLowerCase() )
){
return element;
}
});
setClinicListFilteredSelect(resultFilterSelect);
}
const handleModalContainerClick = (e) => e.stopPropagation();
return (
<>
<div className={`modal ${isOpen && "is-open"}`} onClick={closeModal}>
<div className="modal-container" onClick={handleModalContainerClick}>
<button className="modal-close" onClick={closeModal}>x</button>
{children}
<div>
<h1>Encuentra tu clĂ­nica</h1>
</div>
<div>
<form>
<label>Provincia</label>
<Select
defaultValue={filterSelectClinic}
options={provincesList}
onChange={handleChangeSelect}
/>
</form>
{
clinicListFilteredSelect.map((clinicFilter) => (
<div>
<div>{clinicFilter.title}</div>
<div>{clinicFilter.propsPhone}</div>
<div>{clinicFilter.mobile}</div>
<div>{clinicFilter.email}</div>
<div>{clinicFilter.province} </div>
<div>{clinicFilter.registry}</div>
</div>
))
}
</div>
</div>
</div>
</>
)
}
You need to pass a callback function to the parent and set the state value in the parent, not in the child. (This is known as lifting state up.)
In your case this would involve moving
const [filterSelectClinic, setFilterSelectClinic] = useState<SingleValue<OptionType>>(provincesList[0]);
Into the parent component. Then passing the setFilterSelectClinic function into the child component.
<SearchFilterClinics setFilterSelectClinic={(value) => setFilterSelectClinic(value)} isOpen={isOpen} closeModal={closeModal} clinicFilter={clinicFilter}/>
The value (within the parenthesis) is being passed up by the child component. This is the value you set here:
getClinic().then((response) => {
...
// this is the function we pass in from the parent. It set's the value
// of the callback function to provinceList
setFilterSelectClinic(provinceList);
...
We then setFilterSelectClinic to that value. Meaning filterSelectClinic now has the value passed up in the callback.

How to capture logged user in the array of users?

in my CRUD app, the user on the first screen enters their username and then can create, edit, and delete posts. I have an array of users and an array of posts (I'm using Redux). When a user creates a post, their username needs to appear in a sector within the div to identify who posted the post, but I still can't find a way to get the right user who is logged in to show in the div. How could I capture the specific user who is currently logged in?
I thought of doing something like: when the user typed their username and went to the posts screen, all posts would be linked to this user, but I don't know how to do that
postsSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const postsSlice = createSlice({
name: "posts",
initialState: [],
reducers: {
addPost (state, action) {
state.push(action.payload);
},
editPost(state, action) {
const { id, title, content } = action.payload;
const existingPost = state.find((post) => post.id === id);
if (existingPost) {
existingPost.title = title
existingPost.content = content
}
},
postDeleted(state, action) {
const { id } = action.payload;
const existingPost = state.some((post) => post.id === id);
if (existingPost) {
return state.filter((post) => post.id !== id);
}
},
},
});
export const { addPost, editPost, postDeleted } = postsSlice.actions
export default postsSlice
usersSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: [],
reducers: {
saveUser (state, action) {
state.push(action.payload)
},
}
});
export const { saveUser, replaceUsers } = userSlice.actions;
export default userSlice
mainscreen.js:
import React, { useState, useEffect } from "react";
import "../_assets/App.css";
import "../_assets/mainscreen.css";
import { MdDeleteForever } from "react-icons/md";
import { FiEdit } from "react-icons/fi";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { addPost } from '../redux/postsslice'
import Modal from "../components/modal.jsx";
import EditModal from '../components/editmodal.jsx';
function MainScreen() {
const dispatch = useDispatch();
const navigate = useNavigate();
const user = useSelector((state) => state.user)
const posts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [buttonGreyOut, setButtonGreyOut] = useState("#cccccc");
useEffect(() => {
if (title && content !== "") {
setButtonGreyOut("black");
} else {
setButtonGreyOut("#cccccc");
}
},[title, content]);
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(
addPost({
id: new Date(),
title,
content,
user
})
)
setTitle('')
setContent('')
};
const handleChangeTitle = (text) => {
setTitle(text);
};
const handleChangeContent = (text) => {
setContent(text);
};
const handleButton = (e) => {
e.preventDefault()
navigate("/")
}
const [openEditModal, setOpenEditModal] = useState();
const [openModal, setOpenModal] = useState();
console.log({ posts })
return (
<div className="containerMainScreen">
<button onClick={handleButton}>Back</button>
{openModal && <Modal deleteId={openModal} closeModal={setOpenModal} />}
{openEditModal && <EditModal editId={openEditModal} closeModal={setOpenEditModal} />}
<div className="bar">
<h1>Codeleap</h1>
</div>
<div className="boxPost">
<h2 style={{ fontWeight: 700 }}>What's on your mind?</h2>
<h2>Title</h2>
<form onSubmit={handleSubmitSendPost}>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={(e) => handleChangeTitle(e.target.value)}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={(e) => handleChangeContent(e.target.value)}
></textarea>
<button
className="createButton"
type="submit"
style={{ backgroundColor: buttonGreyOut }}
disabled={!title || !content}
>
CREATE
</button>
</form>
</div>
{posts.slice().reverse().map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(post.id);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(post.id);
}}
style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
/>
</div>
<div id="postowner">
<h3>#{user}</h3>
<br></br>
<textarea style={{ border: "none" }} value={post.content}></textarea>
</div>
</div>
))}
</div>
);
}
export default MainScreen;
Signup:
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { useDispatch } from 'react-redux';
import userSlice from '../redux/userslice';
import { useNavigate } from "react-router-dom";
function Signup() {
const navigate = useNavigate();
const dispatch = useDispatch();
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if (name!== '') {
setButtonGrey("black")
}
else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
dispatch(userSlice.actions.saveUser(name));
navigate("/main")
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input type="text" name="name" value={name} onChange = {e => handleChangeName(e.target.value)} placeholder="Jane Doe" />
<div className="button">
<button type="submit" style={{backgroundColor: buttonGrey}} disabled={!name} >
ENTER
</button>
</div>
</form>
</div>
</div>
);
}
export default Signup;
You can store a current user and maintain a list of all users that ever "signed in".
user.slice.js
const initialState = {
currentUser: '',
users: [],
}
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
saveUser(state, action) {
// person that just signed in
state.currentUser = action.payload;
// list of unique user names
state.users = Array.from(new Set([...state.users, action.payload]));
},
replaceUsers: (state, action) => action.payload
}
});
export const { saveUser } = userSlice.actions;
export default userSlice;
MainScreen.js
Add an author property to each added post, and access the post.author property when mapping the posts.
function MainScreen() {
const dispatch = useDispatch();
const { currentUser } = useSelector((state) => state.user); // <-- current user
const posts = useSelector((state) => state.loadPosts);
...
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(
addPost({
id: uuidV4(),
title,
content,
author: currentUser // <-- current user to post
})
);
setTitle("");
setContent("");
};
...
if (currentUser === "") {
return <Navigate to="/" />;
} else {
return (
<div className="containerMainScreen">
...
{posts.map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(post.id);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(post.id);
}}
style={{
color: "white",
fontSize: "45px",
paddingLeft: "23px"
}}
/>
</div>
<div id="postowner">
<h3>#{post.author}</h3> // <-- user in post object
<br></br>
<textarea
style={{ border: "none" }}
value={post.content}
></textarea>
</div>
</div>
))}
</div>
);
}
}
export default MainScreen;

why my useReducer function is not working.when I use it The page show but function is not working

Hello Everyone . I'm New in react .Every thing is okay But when I'm start using Reducer. It's Not working properly. It's Adding function or deleting function is not working. Why This problem. before using reducer I'm working with context api . and the same project I want work with reducer. but it's not working properly. The page is show but It's Add or remove function is not working
BookContext.jsx
import React, { createContext, useReducer, useEffect } from 'react';
// import { v4 as uuid } from 'uuid' // This is update vertion
import { bookReducer } from '../BookReducer/Reducer';
export const BookContext = createContext();
const BookContextProvider = (props) => {
const [books, dispatch] = useReducer(bookReducer, []);
useEffect(() => {
localStorage.setItem('books', JSON.stringify(books))
}, [books])
return (
<div>
<BookContext.Provider value={{ books, dispatch }}>
{props.children}
</BookContext.Provider>
</div>
);
};
export default BookContextProvider;
Reducer.jsx
import { v4 as uuid } from 'uuid' // This is update vertion
export const bookReducer = (state, action) => {
switch (action.type) {
case ' ADD_BOOK':
return [...state, {
title: action.book.title,
author: action.book.author,
id: uuid()
}]
case 'REMOVE_BOOK':
return state.filter(book => book.id !== action.id)
default:
return state;
}
}
BookForm.jsx
import React, { useContext, useState } from 'react';
import { BookContext } from '../Context/BookContext';
const NewBookform = () => {
const { dispatch } = useContext(BookContext);
const [title, setitle] = useState('');
const [author, setauthor] = useState('');
const Handelesubmit = (e) => {
e.preventDefault()
dispatch({
type: 'ADD_BOOK', book: {
title, author
}
})
setitle(' ');
setauthor(' ');
}
return (
<div>
<form onSubmit={Handelesubmit}>
<input type='text' placeholder='Book Title' value={title} onChange
={(e) => setitle(e.target.value)} required />
<br></br>
<input type='text' placeholder='Book author' value={author} onChange
={(e) => setauthor(e.target.value)} required />
<br></br>
<input type='submit' value=" ADD Book" />
</form>
</div>
);
};
export default NewBookform;
Bookdetails
import React, { useContext } from 'react';
import { BookContext } from '../Context/BookContext';
const Bookldetails = ({ book }) => {
const { dispatch } = useContext(BookContext)
return (
<li onClick={() => dispatch({ type: 'REMOVE_BOOK', id: book.id })}>
<div className="title">{book.title}</div>
<div className="id">{book.author}</div>
</li>
);
};
export default Bookldetails;
Your remove function works just fine, here is your code:
const { createContext, useReducer, useContext } = React;
const uuid = ((id) => () => ++id)(0);
const BookContext = createContext();
const bookReducer = (state, action) => {
switch (action.type) {
case ' ADD_BOOK':
return [
...state,
{
title: action.book.title,
author: action.book.author,
id: uuid(),
},
];
case 'REMOVE_BOOK':
return state.filter((book) => book.id !== action.id);
default:
return state;
}
};
const BookContextProvider = (props) => {
const [books, dispatch] = useReducer(bookReducer, [
{ id: uuid(), title: 'title 1', author: 'author 1' },
{ id: uuid(), title: 'title 2', author: 'author 2' },
]);
//this is not doing anything, you are never reading it
// useEffect(() => {
// localStorage.setItem('books', JSON.stringify(books));
// }, [books]);
return (
<div>
<BookContext.Provider value={{ books, dispatch }}>
{props.children}
</BookContext.Provider>
</div>
);
};
const Bookldetails = ({ book }) => {
const { dispatch } = useContext(BookContext);
return (
<li
onClick={() =>
dispatch({ type: 'REMOVE_BOOK', id: book.id })
}
>
<div className="title">{book.title}</div>
<div className="id">{book.author}</div>
</li>
);
};
const App = () => {
const { books } = useContext(BookContext);
return (
<ul>
{books.map((book) => (
<Bookldetails key={book.id} book={book} />
))}
</ul>
);
};
ReactDOM.render(
<BookContextProvider>
<App />
</BookContextProvider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to optimally update state from parent in double nested child componenet (with hooks)?

Link to video with issue outlined below!
Hello, currently my issue is that, when I click the DeleteButton (found in the SortableItem component), while the mutation successfully works, it requires a refresh to show the deletion. What would be the most optimal way to refresh this as soon as the DeleteButton mutation is run? I believe it requires my updating the state from RankList component within the SortableItem component but would like to know the optimal way to achieve this!
RankList.js
import React, { useContext, useEffect, useRef, useState } from "react";
import gql from "graphql-tag";
import { useQuery, useMutation } from "#apollo/react-hooks";
import { Form } from "semantic-ui-react";
import moment from "moment";
import DeleteButton from "../components/DeleteButton";
import { AuthContext } from "../context/auth";
import { SortableContainer, SortableElement } from "react-sortable-hoc";
import arrayMove from "array-move";
import "../RankList.css";
import { CSSTransitionGroup } from "react-transition-group";
// function SortableItem({ value, listId, listItemId }) {
// return SortableElement(() => (
// <>
// <li className="listLI">{value}</li>
// <DeleteButton listId={listId} listItemId={listItemId} />
// </>
// ));
// }
const SortableItem = SortableElement(
({ deleteItem, value, listId, listItemId }) => (
<>
<li className="listLI">{value}</li>
<DeleteButton
listId={listId}
listItemId={listItemId}
deleteItem={deleteItem}
/>
</>
)
);
const SortableList = SortableContainer(({ deleteItem, items, listId }) => {
return (
<ol className="theList">
{/* <CSSTransitionGroup
transitionName="ranklist"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}
> */}
{items.map((item, index) => (
<SortableItem
deleteItem={deleteItem}
listId={listId}
listItemId={item.id}
key={`item-${item.id}`}
index={index}
value={item.body}
/>
))}
{/* </CSSTransitionGroup> */}
</ol>
);
});
function RankList(props) {
const listId = props.match.params.listId;
const { user } = useContext(AuthContext);
const listItemInputRef = useRef(null);
const [state, setState] = useState({ items: [] });
const [listItem, setListItem] = useState("");
const { loading, error, data } = useQuery(FETCH_LIST_QUERY, {
variables: {
listId,
},
// onError(err) {
// console.log(err.graphQLErrors[0].extensions.exception.errors);
// // setErrors(err.graphQLErrors[0].extensions.exception.errors);
// }
});
useEffect(() => {
if (data && data.getList && data.getList.listItems) {
setState(() => ({ items: data.getList.listItems }));
}
}, [data]);
// const [state, setState] = useState({ items: data.getList.listItems });
const deleteItem = (listItem) => {
let temp = state.items.filter((item) => item.id !== listItem);
console.log(temp);
setState(() => ({ items: temp }));
};
const [submitListItem] = useMutation(SUBMIT_LIST_ITEM_MUTATION, {
update() {
setListItem("");
listItemInputRef.current.blur();
},
variables: {
listId,
body: listItem,
},
});
const [editListItems] = useMutation(EDIT_LIST_ITEMS_MUTATION, {
variables: {
listId,
listItems: state.items,
},
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error..</p>;
function deleteListCallback() {
props.history.push("/");
}
function onSortEnd({ oldIndex, newIndex }) {
setState(({ items }) => ({
items: arrayMove(items, oldIndex, newIndex),
}));
state.items.map((list) => delete list["__typename"]);
editListItems();
}
let listMarkup;
if (!data.getList) {
listMarkup = <p>Loading list...</p>;
} else {
const {
id,
title,
createdAt,
username,
listItems,
comments,
likes,
likeCount,
commentCount,
} = data.getList;
console.log(id);
listMarkup = user ? (
<div className="todoListMain">
<div className="rankListMain">
<div className="rankItemInput">
<h3>{title}</h3>
<Form>
<div className="ui action input fluid">
<input
type="text"
placeholder="Choose rank item.."
name="listItem"
value={listItem}
onChange={(event) => setListItem(event.target.value)}
ref={listItemInputRef}
/>
<button
type="submit"
className="ui button teal"
disabled={listItem.trim() === ""}
onClick={submitListItem}
>
Submit
</button>
</div>
</Form>
</div>
<SortableList
deleteItem={deleteItem}
items={state.items}
listId={id}
onSortEnd={onSortEnd}
helperClass="helperLI"
/>
</div>
</div>
) : (
<div className="todoListMain">
<div className="rankListMain">
<div className="rankItemInput">
<h3>{props.title}</h3>
</div>
{/* <SortableList
items={listItems}
// onSortEnd={onSortEnd}
helperClass="helperLI"
/> */}
<ol className="theList">
{/* <CSSTransitionGroup
transitionName="ranklist"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}
> */}
{listItems.map((item, index) => (
<li
className="listLI"
key={`item-${item.id}`}
index={index}
value={item.body}
>
{item.body}
</li>
))}
{/* </CSSTransitionGroup> */}
</ol>
</div>
</div>
);
}
return listMarkup;
}
const EDIT_LIST_ITEMS_MUTATION = gql`
mutation($listId: ID!, $listItems: [ListItems]!) {
editListItems(listId: $listId, listItems: $listItems) {
id
listItems {
id
body
createdAt
username
}
}
}
`;
const SUBMIT_LIST_ITEM_MUTATION = gql`
mutation($listId: ID!, $body: String!) {
createListItem(listId: $listId, body: $body) {
id
listItems {
id
body
createdAt
username
}
comments {
id
body
createdAt
username
}
commentCount
}
}
`;
const FETCH_LIST_QUERY = gql`
query($listId: ID!) {
getList(listId: $listId) {
id
title
createdAt
username
listItems {
id
createdAt
username
body
}
likeCount
likes {
username
}
commentCount
comments {
id
username
createdAt
body
}
}
}
`;
export default RankList;
DeleteButton.js
import React, { useState } from "react";
import gql from "graphql-tag";
import { useMutation } from "#apollo/react-hooks";
import { Button, Confirm, Icon } from "semantic-ui-react";
import { FETCH_LISTS_QUERY, FETCH_LIST_QUERY } from "../util/graphql";
import MyPopup from "../util/MyPopup";
function DeleteButton({ listId, listItemId, commentId, deleteItem, callback }) {
const [confirmOpen, setConfirmOpen] = useState(false);
let mutation;
if (listItemId) {
mutation = DELETE_LIST_ITEM_MUTATION;
} else if (commentId) {
mutation = DELETE_COMMENT_MUTATION;
} else {
mutation = DELETE_LIST_MUTATION;
}
// const mutation = commentId ? DELETE_COMMENT_MUTATION : DELETE_LIST_MUTATION;
const [deleteListOrComment] = useMutation(mutation, {
update(proxy) {
setConfirmOpen(false);
// remove list from cache
if (!commentId && !listItemId) {
const data = proxy.readQuery({
query: FETCH_LISTS_QUERY,
});
const resLists = data.getLists.filter((p) => p.id !== listId);
proxy.writeQuery({
query: FETCH_LISTS_QUERY,
data: { getLists: [...resLists] },
});
}
if (callback) callback();
},
variables: {
listId,
listItemId,
commentId,
},
onError(err) {
console.log(err.graphQLErrors[0].extensions.exception.errors);
},
});
return (
<>
<MyPopup content={commentId ? "Delete comment" : "Delete list"}>
<Button
as="div"
color="red"
floated="right"
onClick={(e) => {
e.preventDefault();
setConfirmOpen(true);
}}
>
<Icon name="trash" style={{ margin: 0 }} />
</Button>
</MyPopup>
<Confirm
open={confirmOpen}
onCancel={(e) => {
e.preventDefault();
setConfirmOpen(false);
}}
onConfirm={(e) => {
e.preventDefault();
deleteListOrComment();
deleteItem(listItemId);
}}
/>
</>
);
}
const DELETE_LIST_MUTATION = gql`
mutation deleteList($listId: ID!) {
deleteList(listId: $listId)
}
`;
const DELETE_LIST_ITEM_MUTATION = gql`
mutation deleteListItem($listId: ID!, $listItemId: ID!) {
deleteListItem(listId: $listId, listItemId: $listItemId) {
id
comments {
id
username
createdAt
body
}
commentCount
}
}
`;
const DELETE_COMMENT_MUTATION = gql`
mutation deleteComment($listId: ID!, $commentId: ID!) {
deleteComment(listId: $listId, commentId: $commentId) {
id
comments {
id
username
createdAt
body
}
commentCount
}
}
`;
export default DeleteButton;
For this I think of two ways of resolving it.
You could use a state management solution, like redux, flux or context.
Or you could pass a function to the deleteComponent to update the parent (or refetch the items)
Regards.
The question is now updated with functioning code
In the end, I created a deleteItem function in my parent component of RankList and passed this function down the chain all the way to DeleteButton component and executed it in onConfirm to update the parent state.

Resources