How to revalidate data on react-modal close with SWR? - reactjs

I am trying to revalidate the data on react-modal close using SWR in a NextJS project.
I am using the SWR hook like so.
const { data, error, isValidating } = useSWR(
process.env.NEXT_PUBLIC_APP_URL + `/api/users`,
fetcher,{
revalidateIfStale: true,
revalidateOnFocus: true,
revalidateOnMount:true,
}
);
useEffect(() => {
if (data) {
setUsers(data.users);
}
}, [data, isValidating, users]);
//handle loading..
//handle error..
return (
<main className="mx-auto max-w-7xl ">
<Header title="All users"/>
<UsersList users={users} />
</main>
);
I am fetching a list of users and displaying them.
const usersList = users.((user) => (
<div className="space-x-5 text-sm" key={user.id}>
{user.name}
<DisableModal id={user.id} isDisabled={user.active}/>
</div>
));
I have a react modal that allows us to disable the users, once I have disabled the users with handle click.
When the modal closes the data is not being refetched.
This is a sample modal from the docs.
When I close the modal, and can see the list of users. They are not refreshed and not using revalidations with use SWR.
export const DisableModal = ({
id,
isDisabled,
}) => {
const [disableModalIsOpen, setDisableModalIsOpen] = useState(false);
function closeDisableModal() {
setDisableModalIsOpen(false);
}
function openPublishModal() {
setDisableModalIsOpen(true);
}
const handleDisableUser = async () => {
//disable logic in rest call.
closeDisableModal();
}
....
}

You can revalidate the data manually using mutate when the onAfterClose callback in the modal gets triggered.
export const DisableModal = () => {
const [showModal, setShowModal] = useState(false);
const { mutate } = useSWRConfig()
return (
<>
<button onClick={() => { setShowModal(true) }}>Trigger Modal</button>
<ReactModal
isOpen={showModal}
onAfterClose={() => {
mutate(process.env.NEXT_PUBLIC_APP_URL + '/api/users')
}}
contentLabel="Minimal Modal Example"
>
<button onClick={() => { setShowModal(false) }}>Close Modal</button>
</ReactModal>
</>
)
}
Calling mutate(process.env.NEXT_PUBLIC_APP_URL + '/api/users') will broadcast a revalidation message to SWR hook with that given key. Meaning the useSWR(process.env.NEXT_PUBLIC_APP_URL + '/api/users', fetcher, { ... }) hook will re-run and return the updated users data.

Related

how to show a new todo-item without refreshing the page?

I tried a lots of things , and this problem does not seem to go away , can someone help me with this ??
this is my app component :
function App() {
const [todo, setTodo] = useState([]);
async function getTodo() {
try {
const todo = await axios.get("http://localhost:5000/api/todos");
// console.log(todo.data)
setTodo(todo.data);
} catch (error) {
console.log("something is wrong");
}
}
useEffect(() => {
// Update the document title using the browser API
getTodo();
}, []);
return (
<div className="App">
<h1>My Todo List</h1>
<h2>My Todo List</h2>
<Task Todor={todo} />
<Write />
</div>
);
}
export default App;
and this is my todos component :
function Todos({ Todor }) {
return (
<div className="Todos">
{Todor.map(T => <Todo post={T} />)}
</div>
);
}
export default Todos;
and this is my todo component :
function Todo({ post }) {
return (
<div className="Todo">
<h2>{post.title}</h2>
</div>
);
}
export default Todo ;
and this my add component :
export default function Write() {
const [inputText, setInputText] = useState({
title: ""
});
function handleChange(e) {
setInputText({
...inputText,
[e.target.name]: e.target.value,
});
}
const [status, setStatus] = useState(false);
async function addItem(e) {
e.preventDefault();
const res = await axios.post("http://localhost:5000/api/todos", inputText);
setInputText(inputText)
console.log("response:", res)
setStatus(true);
setInputText("");
}
return (
<div className="container">
<div className="form">
<input onChange={handleChange} type="text" name="title" />
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
</div>
);
}
the new items dont show until I refresh the page , how to do that without refreshing ?
because obviously that defeats the purpose of React !!
useEffect(() => {
// Update the document title using the browser API
getTodo();
}, []);
The code inside useEffect with empty dependencies array [] only runs on the first render, to run it on every render you should remove the empty array dependencies.
useEffect(() => {
// Update the document title using the browser API
getTodo();
});
Note: It is not a best practice because your component will invoke getTodo() every time rerendered. In your case, you can use a state variable to control where to re-run the getTodo funtion e.g:
const [isAddedSuccess, setIsAddedSuccess] = useState(false)
Everytime you add new item successfully, just setIsAddedSuccess(true) and your useEffect should look like below:
useEffect(() => {
// Update the document title using the browser API
if (isAddedSuccess) getTodo();
}, [isAddedSuccess]);

How do I toggle between buttons in React?

I am trying to toggle between add and remove buttons in reactjs, it works fine until I reload the page, how do I make this change persist? as the button changes to "add to bin" from "remove from bin" on reload. Below is my code explaining this:
import { useMutation } from "#apollo/client";
import { UPDATE_IMAGE } from "./mutation";
import { useState } from 'react';
function NewBin(props) {
const [uu, {err}] = useMutation(UPDATE_IMAGE);
const [toggle,setToggle] = useState(false)
const addBin = async () => {
await uu({
variables: {
id: props.data.id,
url: props.data.url,
description: props.data.description,
posterName: props.data.posterName,
binned: true,
userPosted: props.data.userPosted
},
});
};
const removeBin = async () => {
await uu({
variables: {
id: props.data.id,
url: props.data.url,
description: props.data.description,
posterName: props.data.posterName,
binned: false,
userPosted: props.data.userPosted
},
});
};
const comp1 = async () => {
addBin();
setToggle(true);
}
const comp2 = async () => {
removeBin();
setToggle(false);
}
return (
<div className="Appp">
{toggle ? <button onClick={() => comp2()}>Remove from Bin</button>
: <button onClick={() => comp1()}>Add to Bin</button>
}
</div>
);
}
export default NewBin;
NewBin's parent:
function UnsplashPosts() {
const classes = useStyles();
const { loading, error, data } = useQuery(unsplashImages);
if(error) {
return <h1> error</h1>;
}
if(loading) {
return <h1> loading</h1>;
}
return (
<div className="App">
{data.unsplashImages.map((data) => (
<Card className={classes.card} variant='outlined'>
<CardHeader className={classes.titleHead} title={data.posterName} />
<CardMedia
className={classes.media}
component='img'
image={data.url}
title='image'
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='span'>
<p>{data.description}</p>
<NewBin data={data}/>
<br/>
<br/>
<br></br>
</Typography>
</CardContent>
</Card>
))}
</div>
);
}
The binned field shows true or false if it is in the bin or not.
You can persist the toggle state to localStorage, and initialize from localStorage.
Use a state initializer function to read from localStorage and provide the initial state value.
Use an useEffect hook to persist the updated toggle state to localStorage upon update.
Example:
function NewBin(props) {
...
const [toggle, setToggle] = useState(() => {
// Load saved state from localStorage or provide fallback
return JSON.parse(localStorage.getItem("toggle")) ?? false;
});
useEffect(() => {
// Persist state to localStorage
localStorage.setItem("toggle", JSON.stringify(toggle));
}, [toggle]);
...

React: Assigning array to variable using useState to pass into modal

I made a JSON file for the upcoming NFL season. In this component I have a working fetch method that gets my data, and I've named the variable "squads". Now I want to press a button to filter out the selected team's schedule and display it in a modal. I've hard coded my button in this example. My modal component works fine, and I have {props.children} in the modal's body to accept my data.
In the code below you'll see that I'm trying to assign the filtered team to the selectedTeam variable using useState. The error message I'm getting just says my variables are undefined.
import React, { useState, useEffect } from "react";
import Modal from "./Components/Modal";
export default function App() {
const [show, setShow] = useState(false);
const [title, setTitle] = useState("");
const [squads, setSquads] = useState([]);
const [modalTitleBackground, setModalTitleBackground] = useState("");
const [image, setImage] = useState("");
const [selectedTeam, setSelectedTeam] = useState([]);
const url = "../nfl2021.json";
const fetchData = async () => {
try {
const response = await fetch(url);
const data = await response.json();
setSquads(data.teams);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchData();
}, []);
// const filterTeam = (team) => {
// const theTeam = squads.filter((squad) => squad.name === team);
// setModalTitleBackground(theTeam[0].topBG);
// // setTitle(theTeam[0].name);
// setNickname(theTeam[0].nickname);
// setImage(`./images/${theTeam[0].img}`);
// setShow(true);
// };
const filterTeam = (team) => {
setSelectedTeam(squads.filter((squad) => squad.name === team));
console.log(selectedTeam);
setTitle(selectedTeam[0].name);
setModalTitleBackground(selectedTeam[0].topBG);
setImage(`./images/${selectedTeam[0].img}`);
setShow(true);
};
return (
<div className="App">
<button onClick={() => filterTeam("New England Patriots")}>
Show Modal
</button>
<button onClick={() => filterTeam("Green Bay Packers")}>
Show Modal 2
</button>
<button onClick={() => filterTeam("Cincinnati Bengals")}>
Show Modal 3
</button>
<Modal
image={image}
title={title}
backgroundColor={modalTitleBackground}
onClose={() => setShow(false)}
show={show}
>
<p>
This is the modal body using props.children in the modal component.
</p>
<p>The {title} 2021 schedule.</p>
{selectedTeam[0].schedule.map((schedule, index) => {
return (
<p>
Week {index + 1}: The {selectedTeam[0].nickname} play the{" "}
{selectedTeam[0].schedule[index].opponent}.
</p>
);
})}
</Modal>
</div>
);
}
1- In react, the state is set asynchronously. selectedTeam is not set until next render.
2- You can use find instead of filter and get rid of array access.
const [selectedTeam, setSelectedTeam] = useState({schedule: []});
...
const filterTeam = (team) => {
let temp = squads.find((squad) => squad.name === team);
setSelectedTeam(temp);
console.log(temp);
setTitle(temp.name);
setModalTitleBackground(temp.topBG);
setImage(`./images/${temp.img}`);
setShow(true);
};
...
{selectedTeam.schedule.map((match, index) => {
return (
<p>
Week {index + 1}: The {selectedTeam.nickname} play the {match.opponent}.
</p>
);
})}

Avoid re-creating body component on each render of react-modal

I'm using this lib to create a modal
I have 3 components: Table, Modal and List
Table has Modal (a custom React Modal), and the body of Modal will be List.
Now the problem is, List has some functions which change the states of Table, so when I do something that can make Table's state change, Table and Modal will be re-rendered when Modal is re-rendered, it re-creates a new List which leads to lost all stuffs I'm doing with List.
Here is a simple version of my app. link
Now I don't want List to be re-created each time Modal is re-rendered. Is there any way to archive that? (I don't want to create a modal myself or use global state management in this case)
import { useEffect, useMemo, useState } from "react";
import ReactModal from "react-modal";
ReactModal.setAppElement("#root");
const List = ({ onClick }) => {
useEffect(() => {
console.log("List is mounted");
}, []);
return <button onClick={onClick}>Click me!</button>;
};
const Modal = ({ state, body, isOpen }) => {
useEffect(() => {
console.log("Modal is re-rendered");
});
return (
<div
id="react modal wrapper"
style={{
display: `${isOpen ? "block" : "none"}`
}}
>
<ReactModal isOpen={isOpen}>
<div>
state is {state}
<br />
{body}
</div>
</ReactModal>
</div>
);
};
const Table = ({ state, onClick, isOpen }) => {
useEffect(() => {
console.log("Table is re-rendered");
});
const memorizedList = useMemo(() => <List onClick={onClick} />, []);
return (
<div>
state: {state}
<Modal state={state} body={memorizedList} isOpen={isOpen} />
</div>
);
};
const App = () => {
const [state, setState] = useState(1);
const onClick = () => setState((v) => v + 1);
return (
<div>
<button onClick={onClick}>Change state</button>
<Table state={state} onClick={onClick} isOpen={state % 2 === 0} />
</div>
);
};
export default App;

node.current is not a function

I am trying to create a function close modal when click outside but I am keep getting this error:
TypeError: node.current is not a function
Here is my following code in MemberCard.js:
const [modalStatus, setModalStatus] = useState(false);
const node = useRef(null);
const openModal = () => {
setModalStatus(!modalStatus);
};
const handleClick = (e) => {
if (node.current(e.target)) {
return;
}
// outside click
setModalStatus(false);
};
useEffect(() => {
document.addEventListener("mousedown", handleClick);
return () => {
document.removeEventListener("mousedown", handleClick);
};
}, []);
return (
<div className="member-card">
<div className="member-edit" onClick={openModal}>
<Symlink />
</div>
{modalStatus && (
<TeamStatusModal
active={modalStatus}
ref={node}
tab={tab}
member={member}
/>
)}
...
}
Here is my modal that I open after click:
const TeamStatusModal = (props) => {
const { active, tab, member, ref } = props;
console.log(ref);
return (
<div
className={`team-status-modal-container ${active ? "ACTIVE_CLASS" : ""}`}
>
<button className="status">
<ProfileIcon /> <span>View Profile</span>
</button>
<hr />
<button className="status">
<MessageIcon /> <span>Message Me</span>
</button>
</div>
);
};
How can I implement this feature?
In react, there are some good libraries that can help you with modals, one of them is called react-modal, you can give it a check.
If you want to implement a modal by yourself, we can follow some steps.
First we need to define a context, because the modal state needs to be accesed by more than one component or page in your app.
In the context, you could store the modal in a isModalOpen state, and add functions to manipulate it, such as openModal and closeModal. It really depends on the amount of features you want to add to this implementation.
Finally, you make the context globally accessible wrapping your app around a provider.
an example implementation
const ModalContext = createContext({})
export const ModalContextProvider = ({children}) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const toggleModalState = () => {
setIsModalOpen(state => !state)
}
return <ModalContext.Provider value={{isModalOpen, toggleModalState}}>{children}<ModalContext.Provider>
}
export const useModal = () => {
return useContext(ModalContext)
}
Now the modal will be available globally

Resources