Database not updating - React.js, Moralis - reactjs

I'm having issues updating my database when saving edit. I am trying to retrieve Github data via API and with that data, add it to the Moralis database. I added another button outside of the form because I presume the saveEdit function will have to run after the form has been submitted. Code is as follows:
export const Dashboard = () => {
const [userSearch, setUserSearch] = useState<string>("");
const [foundUser, setFoundUser] = useState<IGitHubUser>();
const performSearchRequest = async () => {
try {
const response = await axios.get<IGitHubUser>(
`https://api.github.com/users/${userSearch}`
);
setFoundUser(response.data);
} catch (error) {
console.log(error);
}
};
const searchForUser = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
performSearchRequest();
};
const { Moralis, isInitialized } = useMoralis();
const user = isInitialized ? Moralis.User.current() : undefined;
const saveEdits = async () => {
const User = Moralis.Object.extend("_User");
const query = new Moralis.Query(User);
const myDetails = await query.first();
if (foundUser) {
myDetails?.set("github", foundUser.name);
console.log("details saved");
}
try {
await myDetails?.save();
} catch (err) {
console.log(err);
}
window.location.reload();
};
return (
<>
<h2>Search for a user</h2>
<form className="search-user" onSubmit={searchForUser}>
<input
value={userSearch}
onChange={(e) => setUserSearch(e.target.value)}
placeholder="Enter a username..."
/>
<button>Search</button>
</form>
<button onClick={saveEdits}>Search</button>
</>
);
};

Related

How can I deal with the state updating asynchronously?

I have a login page where if a user enters the wrong credentials or submits blank fields, an error will be displayed on the page. Currently, when a user fails to signin with the right credentials, the error will only be displayed on the second click. I'm aware that my current problem is due to the state updating asynchronously, but I'm not sure how to resolve the problem in my code:
onst Login: React.FC<Props> = () => {
const user = useAppSelector(selectUser);
const auth = useAppSelector(selectAuth);
const dispatch = useAppDispatch();
...
const [signInError, setSignInError] = useState<boolean>(false);
const handleSignInError = () => {
if (auth.error?.status === 401 && auth.error.message === Constants.Errors.WRONG_CREDENTIALS) {
setSignInError(true);
}
}
const renderSigninError = () => {
if (signInError) {
return (
<Box paddingTop={2.5}>
<Alert variant="outlined" severity="error">
{Constants.Auth.FAILED_SIGN_IN}
</Alert>
</Box>
);
} else {
return (
<div/>
);
}
}
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData: any = new FormData(event.currentTarget);
const loginData: LoginData = {
email: formData.get("email"),
password: formData.get("password"),
}
try {
const res: any = await dispatch(login(loginData));
const resType: string = res.type;
if (resType === "auth/login/fulfilled") {
const userPayload: UserLogin = res.payload;
const loginUser: UserInterface = {
...
}
setSignInError(false);
dispatch(setUser(loginUser))
navigate("/");
}
else {
console.log("Failed to login");
handleSignInError();
}
}
catch (error: any) {
console.log(error.message);
}
}
return (
<Box
...code omitted...
{renderSigninError()}
...
)
}
What can I do to make sure that when the app loads and the user fails to login on the first click, the state for signInError should be true and the error displays?
You have at least 2 options.
add a conditional component.
Add a useEffect listenning for signInError and handle there as you want. It will trigger everytime signInError state changes
import React from 'react';
const [signInError, setError] = useState(false)
import React from 'react';
const [signInError, setError] = useState(false)
useEffect(() => {
console.log('new signInError value >', signInError)
// do whatever you want
}, [signInError])
export function App(props) {
return (
<div className='App'>
{
signInError ? (<p>something happened</p>) : null
}
</div>
);
}
There might be better approaches. Hope this can help you
I found a work around by changing handleSignInError() to update the state directly through setSignInError(true) as in:
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData: any = new FormData(event.currentTarget);
const loginData: LoginData = {
email: formData.get("email"),
password: formData.get("password"),
}
try {
const res: any = await dispatch(login(loginData));
const resType: string = res.type;
if (resType === "auth/login/fulfilled") {
const userPayload: UserLogin = res.payload;
const loginUser: UserInterface = {
...
}
setSignInError(false);
dispatch(setUser(loginUser))
navigate("/");
}
else {
console.log("Failed to login");
setSignInError(true); //changed here
}
}
catch (error: any) {
console.log(error.message);
}
}
Could someone help me understand why using another function initially didnt work?

how to make useState hook act like synchronous function?

here is my code,
const App= () => {
const [someData, setSomeData] = useState('');
const [token, setToken] = useState('');
const fetchingAPI = async () => {
try {
const response = await someAPI();
const data = response.data.data;
const tokenData = response.data.token;
setSomeData(data);
setToken(tokenData);
return true;
} catch (err) {
console.log(err)
return false;
}
};
const onSubmitHandler = async (e: any) => {
e.preventDefault();
const fetchHandler = async () => {
const data = await fetchingAPI();
return data ? e.target.submit() : '';
};
fetchHandler();
};
return (
<div>
<button onClick={toPreviousStep}>previous step</button>
<form
action="https://somewebsite.com"
method="POST"
onSubmit={onSubmitHandler}>
<button>
<input type="hidden" name="someData" value={someData} />
<input type="hidden" name="token" value={token} />
confirm
</button>
</form>
</div>
);
};
when user click confirm button, I would try to fetch some data from backend and store it in a state.
If nothing goes wrong, it would be submitted and redirect to another website.
However, the problem is state doesn't change immediately, so it just bring an empty string to the third party website.
how should I fix this code? thank you.
I solve this problem with document.querySelector, and here is my code
const App= () => {
const fetchingAPI = async () => {
const data = document.querySelector<HTMLInputElement>.
('#data');
const token = document.querySelector<HTMLInputElement>('#token');
try {
const response = await someAPI();
if (data) data.value = response.data.data;
if (token) token.value = response.data.token;
return true;
} catch (err) {
console.log(err)
return false;
}
};
const onSubmitHandler = async (e: any) => {
e.preventDefault();
const fetchHandler = async () => {
const data = await fetchingAPI();
return data ? e.target.submit() : '';
};
fetchHandler();
};
return (
<div>
<button onClick={toPreviousStep}>previous step</button>
<form
action="https://somewebsite.com"
method="POST"
onSubmit={onSubmitHandler}>
<button>
<input type="hidden" name="someData" value="data" id="data" />
<input type="hidden" name="token" value="token" id="token" />
confirm
</button>
</form>
</div>
);
};
It makes both data and token input change immediately.

why is recipe.map not a function?

I am trying to pass data into a component, however I am getting an error it is saying recipe.map is not a component. Would love some help.
const App = () => {
const [recipe, setRecipe] = useState([]);
const appId = `af783d30`;
const appKey = ``;
const url = `https://api.edamam.com/search?q=chicken&app_id=${appId}&app_key=${appKey}&from=0&to=3&calories=591-722&health=alcohol-free`;
console.log(url);
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(url);
setRecipe(res.data);
console.log(res.data);
} catch (err) {
console.error(err);
}
};
fetchData();
}, []);
return (
<div>
{recipe.map((r) => {
return <RecipeCard recipe={r} />;
})}
</div>
);
};
export default App;

Trigger react useEffect

I'm fetching data from a firebase db it works when the component renders, but I can't make it to fetch again when there is a new entry in my db.
What I've tried
I've tried passing a state to the dependency array of useEffect and I changed that state every time my form was submitted (That's the time when there's a new entry in my db)
App
function App() {
const [showForm, setShowForm] = useState(true);
const [tasks, setTasks] = useState([]);
const [isSubmitted, setIsSubmitted] = useState(true);
//Fetch tasks from server
const fetchData = () => {
fetch(
"https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json"
)
.then((response) => {
return response.json();
})
.then((data) => {
const tasks = [];
//Convert the data to an array so i can map over it
for (const key in data) {
const task = {
id: key,
...data[key],
};
tasks.push(task);
}
setTasks(tasks);
});
};
useEffect(() => {
fetchData();
}, [isSubmitted]);
//Show/Hide form
const onAddHandler = () => {
setShowForm(!showForm);
};
const formSubmitted = () => {
setIsSubmitted(!isSubmitted);
console.log(isSubmitted);
};
return (
<Container>
<Header click={onAddHandler} isShown={showForm}></Header>
{showForm ? <Form fs={formSubmitted}></Form> : ""}
<Tasks tasks={tasks}></Tasks>
</Container>
);
}
export default App;
Form
function Form(props) {
const [task, setTask] = useState();
const [dayTime, setDayTime] = useState();
const [reminder, setReminder] = useState();
//Posting Form data to firebase (DUMMY API)
const postFormData = (fullTask) => {
fetch(
"https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json",
{
method: "POST",
body: JSON.stringify(fullTask),
headers: {
"Content-Type": "application/json",
},
}
);
};
//Make an object of form data
const onSubmit = (e) => {
e.preventDefault();
const fullTask = {
task: task,
dayTime: dayTime,
reminder: reminder,
};
//Post func call
postFormData(fullTask);
props.fs();
//Field clearing
setTask("");
setDayTime("");
setReminder("");
};
return (
<AddForm onSubmit={onSubmit}>
<FormControl>
<Label>Task</Label>
<Input
type="text"
placeholder="Add Task"
onChange={(e) => setTask(e.target.value)}
value={task}
required
></Input>
</FormControl>
<FormControl>
<Label>Day & Time</Label>
<Input
type="text"
placeholder="Add Task"
onChange={(e) => setDayTime(e.target.value)}
value={dayTime}
required
></Input>
</FormControl>
<FromControlCheck>
<CheckLabel>Set Reminder</CheckLabel>
<CheckInput
type="checkbox"
onChange={(e) => setReminder(e.currentTarget.checked)}
value={reminder}
></CheckInput>
</FromControlCheck>
<Submit type="submit" value="Save Task"></Submit>
</AddForm>
);
}
export default Form;
I would pass fetchData as a props to <Form>. When submitted, I would call it.
Form
const onSubmit = async (e) => {
e.preventDefault();
const fullTask = {
task: task,
dayTime: dayTime,
reminder: reminder,
};
//Post func call
await postFormData(fullTask);
await props.fetchData();
//Field clearing
setTask("");
setDayTime("");
setReminder("");
};
Then remove the isSubmitted state.
Try change the "Id" value to "id". Try make it the same name as the key for the id in "fecthData" function.
I think this solve your problem
function App() {
const [showForm, setShowForm] = useState(true);
const [tasks, setTasks] = useState([]);
const [isSubmitted, setIsSubmitted] = useState(false);
//Fetch tasks from server
const fetchData = () => {
fetch(
"https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json"
)
.then((response) => {
return response.json();
})
.then((data) => {
const tasks = [];
//Convert the data to an array so i can map over it
for (const key in data) {
const task = {
id: key,
...data[key],
};
tasks.push(task);
}
setTasks(tasks);
});
};
useEffect(() => {
if (isSubmitted) {
fetchData();
setIsSubmitted(false);
}
}, [isSubmitted]);
//Show/Hide form
const onAddHandler = () => {
setShowForm(!showForm);
};
const formSubmitted = () => {
setIsSubmitted(true);
console.log(isSubmitted);
};
return (
<Container>
<Header click={onAddHandler} isShown={showForm}></Header>
{showForm ? <Form fs={formSubmitted}></Form> : ""}
<Tasks tasks={tasks}></Tasks>
</Container>
);
}
export default App;

Testing/mocking a service inside functional container in React/Jest

I am stuck in a weird scenario. I wrote a functional component a few days back. It had state hooks in it and also context hooks. I am calling an API in a function based on a click. Here's the function
const gameService = new Game();
const Toolbar = () => {
const [name, setName] = useState("");
const [addVisible, setAddVisible] = useState(false);
const [loading, setLoading] = useState(false);
const errorContext = useContext(ErrorContext);
const appContext = useContext(AppContext);
const addGame = async () => {
setLoading(true);
try {
const updatedGames = await gameService.add({
name
});
appContext.setGames(updatedGames);
setLoading(false);
setName('');
setAddVisible(false);
errorContext.setError(undefined);
} catch (e) {
errorContext.setError(e);
setLoading(false);
}
};
return (
<StyledToolbar>
Games
<IconButton
style={{ float: "right" }}
onClick={() => setAddVisible(true)}
>
<Plus size={12} />
</IconButton>
<br />
{addVisible && (
<div className="form">
<form onSubmit={() => addGame()}>
<label htmlFor="name">Board Name</label>
<input
disabled={loading}
id="name"
name="name"
placeholder="My Board 1"
value={name}
onChange={e => setName(e.target.value)}
/>
<div style={{ float: "right" }}>
<IconButton type="submit" disabled={loading}>
<ArrowRight size={15} />
</IconButton>
<IconButton
disabled={loading}
onClick={() => {
setAddVisible(false);
setName("");
}}
>
<X size={15} />
</IconButton>
</div>
</form>
</div>
)}
</StyledToolbar>
);
};
This is game Service which is handling the API calls
class Game {
constructor() {
this.instance = axios.create({
baseURL: API_URL,
timeout: TIMEOUT
});
}
getAll() {
return new Promise(async (resolve, reject) => {
try {
const game = await this.instance.get('/games');
resolve(game.data);
} catch (e) {
reject(e.message);
}
});
}
getById(id) {
return new Promise(async (resolve, reject) => {
try {
const game = await this.instance.get(`/games/${id}`);
resolve(game.data);
} catch (e) {
reject(e.message);
}
});
}
update(id, body) {
return new Promise(async (resolve, reject) => {
try {
const game = await this.instance.put(`/games/${id}`, body);
resolve(game.data);
} catch (e) {
reject(e.message);
}
});
}
add(body) {
return new Promise(async (resolve, reject) => {
try {
const game = await this.instance.post('/games', body);
resolve(game.data);
} catch (e) {
reject(e.message);
}
});
}
delete(id) {
return new Promise(async (resolve, reject) => {
try {
const game = await this.instance.delete(`/games/${id}`);
resolve(game.data);
} catch (e) {
reject(e.message);
}
});
}
}
and this is so far test I've written and I am not able to figure out how I can test "Add" method of this class. I am trying to simulate the click and trying to mock/spy on add method.
describe("Toolbar Add functionality", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<Toolbar />);
wrapper.find("IconButton").simulate("click");
});
it("Should make an API call when clicked add button", () => {
jest.mock("../services/game");
const gameService = new Game();
console.log(gameService);
const getSpy = jest.spyOn(gameService, "add");
const inputField = wrapper.find("form").find("input");
inputField.simulate("change", { target: { value: "name" } });
wrapper
.find("form")
.find("IconButton[type='submit']")
.simulate("click");
expect(getSpy).toBeCalled();
});
});
I need help in finding out how I can mock service in a similar scenario because I have the same issue in multiple containers where I am fetching data and updating the calls.
You need to wait for the promise to resolve setting your test as async and using await / act, this is the method I use but you can find other options by googling jest asynchronous testing
import { act } from 'react-dom/test-utils';
...
describe("Toolbar Add functionality", async () => {
let wrapper;
let gameService;
beforeEach(() => {
// I'm not sure about this arragement for gameService creation though
jest.mock("../services/game");
const gameService = new Game();
wrapper = mount(<Toolbar gameService={gameService} />);
wrapper.find("IconButton").simulate("click");
});
it("Should make an API call when clicked add button", () => {
console.log(gameService);
const getSpy = jest.spyOn(gameService, "add");
const inputField = wrapper.find("form").find("input");
inputField.simulate("change", { target: { value: "name" } });
wrapper
.find("form")
.find("IconButton[type='submit']")
.simulate("click");
await act(async () => await Promise.resolve());
expect(getSpy).toBeCalled();
});
});
UPDATE
Because your Game service is a class and you are creating new objects to use it, it will be necessary to pass it as a prop so you will be able to spy on the actual object the component is using
const gameService = new Game();
const Toolbar = ({gameService}) => {
const [name, setName] = useState("");
...
}

Resources