POST request to JSONPlaceholder not taking effect in later fetch - javascript

I have sorted the posts with get method using typicode. Now I want to add it myself using the post method. How can I do it properly?
The problem here is that even if it is posted, it does not appear in the posts list. I want it to appear in all posts when I add it myself and see it in the ranking. How can I do it?
import axios from "axios";
import React from "react";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
const Form = ({ mainData, setData }) => {
const [formData, setFormData] = useState({
title: "",
body: "",
});
const history = useNavigate();
const onChange = (e) => {
setFormData({[e.target.name]: e.target.value });
};
const onSubmit = (e) => {
e.preventDefault();
history("/");
axios
.post("https://jsonplaceholder.typicode.com/posts", formData)
.then((res) => {
console.log(res);
});
};
return (
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="your title"
name="title"
value={formData.title}
onChange={onChange}
/>
<input
type="text"
placeholder="body"
name="body"
value={formData.body}
onChange={onChange}
/>
<button type="submit">Submit</button>
</form>
);
};
export default Form;
import React from "react";
import axios from "axios";
import { useEffect, useState } from "react";
import { NavLink } from "react-router-dom";
const Posts = ({mainData, setData}) => {
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/posts")
.then((res) => res)
.then((res) => {
setData(res.data);
console.log(mainData);
});
}, []);
return (
<>
<NavLink to="/new">Add new post</NavLink>
{mainData?.map((item, index) => (
<h4 key={index}>
{item.id} : {item.title}
</h4>
))}
</>
);
};
export default Posts;
import React, {useState} from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Form from "../Form";
import Posts from "../Posts";
const Rout = () => {
const [mainData, setData] = useState([]);
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Posts mainData={mainData} setData={setData}/>} ></Route>
<Route path="/new" element={<Form mainData={mainData} setData={setData}/>}></Route>
</Routes>
</BrowserRouter>
);
};
export default Rout;

A post request made to JSONPlaceholder is juste for testing, it doesn't really get registered in its database. Here a quote from their doc:
Important: resource will not be really updated on the server but it will be faked as if.
When you redirect to "/", Posts will fetch data but it won't contain what you just added inside Form.

Related

React passing props to other components

Hello I am having trouble passing props between components. I can't share the exact code so I made a simplified version. I am not getting any console errors, though login is obviously 'undefined' Any insight is appreciated!
App.js
import React, { useState } from "react";
function App() {
const [login, setLogin] = useState('Jpm91297');
const changeState = () => {
const newLogin = document.getElementById('loginData').value;
setLogin(newLogin);
}
return (
<>
<h1>Fancy API Call!!!</h1>
<form onSubmit={() => changeState()}>
<input type='text' id='loginData'></input>
<button>Submit</button>
</form>
</>
);
}
export default App;
Api.Js
import React, {useEffect, useState} from "react";
const Api = ( { login } ) => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`https://api.github.com/users/${login}`)
.then((res) => res.json())
.then(setData);
}, []);
if (data) {
return <div>{JSON.stringify(data)}</div>
}
return <div>No data Avail</div>
}
export default Api;
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Api from './api'
ReactDOM.render(
<>
<App />
<Api />
</>,
document.getElementById('root')
);
You are not preventing the default form action from occurring. This reloads the app.
You should lift the login state to the common parent of App and Api so it can be passed down as a prop. See Lifting State Up.
Example:
index.js
Move the login state to a parent component so that it can be passed down as props to the children components that care about it.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Api from './api';
const Root = () => {
const [login, setLogin] = useState('Jpm91297');
return (
<>
<App setLogin={setLogin} />
<Api login={login} />
</>
);
};
ReactDOM.render(
<Root />,
document.getElementById('root')
);
App
Pass the changeState callback directly as the form element's onSubmit handler and prevent the default action. Access the form field from the onSubmit event object.
function App({ setLogin }) {
const changeState = (event) => {
event.preventDefault();
const newLogin = event.target.loginData.value;
setLogin(newLogin);
}
return (
<>
<h1>Fancy API Call!!!</h1>
<form onSubmit={changeState}>
<input type='text' id='loginData'></input>
<button type="submit">Submit</button>
</form>
</>
);
}
Api
const Api = ({ login }) => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`https://api.github.com/users/${login}`)
.then((res) => res.json())
.then(setData);
}, [login]); // <-- add login dependency so fetch is made when login changes
if (data) {
return <div>{JSON.stringify(data)}</div>;
}
return <div>No data Avail</div>;
};

I try to lay out a match for receiving a name from url but I receive a match undefinde

I encountered such an error match undefind. I'm taking an old course on React did everything as shown in the lesson but I get an error why. i don't understand why match undefinde. Maybe you need to pick up the match in another way or somehow pass it ??
import React from "react";
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { NavBar } from "./components/NavBar";
import { Home } from './pages/Home'
import { About } from './pages/About'
import { Profile } from './pages/Profile'
import { Alert } from "./components/Alert";
import { AlertState } from "./context/alert/AlertState";
import { GithubState } from "./context/github/GithunState";
function App() {
return (
<GithubState>
<AlertState>
<BrowserRouter>
<NavBar />
<div className="container pt-4">
<Alert alert={{text: 'Test Alert'}} />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profile/:name" element={<Profile />} />
</Routes>
</div>
</BrowserRouter>
</AlertState>
</GithubState>
);
}
export default App;
import React from 'react'
import { useContext, useEffect } from 'react';
import { GithubContext } from '../context/github/githubContex';
export const Profile = ({match}) => {
// const github = useContext(GithubContext)
// const name = match.params.name
// useEffect(() => {
// github.getUser()
// github.getRepos(name)
// }, [])
console.log('asd',match);
return(
<div>
<h1>Profile page</h1>
</div>
)
}
import React, {useReducer} from "react"
import axios from 'axios'
import { CLEAR_USERS, GET_REPOS, GET_USER, SEARCH_USERS, SET_LOADING } from "../types"
import { GithubContext } from "./githubContex"
import { githubReducer } from "./githubReducer"
const CLIENT_ID = process.env.REACT_APP_CLIENT_ID
const CLIENT_SECRET = process.env.REACT_APP_CLIENT_SECRET
const withCreads = url => {
return `${url}client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}`
}
export const GithubState = ({children}) => {
const initialState = {
user: {},
users: [],
loading: false,
repos: []
}
const [state, dispatch] = useReducer(githubReducer, initialState)
const search = async value => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/search/users?q=${value}&`)
)
dispatch({
type: SEARCH_USERS,
payload: response.data.items
})
}
const getUser = async name => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/users/users/${name}?`)
)
dispatch({
type: GET_USER,
payload: response.data
})
}
const getRepos = async name => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/users/users/${name}/repos?per_page=5&`)
)
dispatch({
type: GET_REPOS,
payload: response.data
})
}
const clearUsers = () => dispatch({type: CLEAR_USERS})
const setLoading = () => dispatch({type: SET_LOADING})
const {user, users, repos, loading} = state
return (
<GithubContext.Provider value={{
setLoading, search, getUser, getRepos, clearUsers,
user, users, repos, loading
}}>
{children}
</GithubContext.Provider>
)
}
link to Github https://github.com/Eater228/React-Hooks
Check your package.json file and if you are using an older version of react-router-dom please use the latest version.
match prop should be passed down from the Route component and it will reflect the correct data as you are using react-router-dom.
Update
You are using element prop for rendering component and that's not the correct one. You should replace that element with component and it will work.
Update
Please consider using useParams hook instead of that match prop.
https://reactrouter.com/docs/en/v6/getting-started/overview#reading-url-parameters

react-js testing library unable to test useState hook and asynchronous axios signIn function

I cant seem to get the test for my reactjs login component to work.
I am using react-testing-library only and not Enzyme.
I want to be able test 3 things:
That the signIn is clicked once and the axios ajax call uses the mocked ajax
That I can set a value using useState and check the value was set
That the page rendered after a successful login has the text welcome
None of the above is working. I have been working through different issues eg this expect(signIn).toHaveBeenCalledTimes(1) returns:
Expected number of calls: 1
Received number of calls: 0
Other times it throws Error: Unable to find the "window" object for the given node. for this:
const button = screen.getByRole('button', {name: /submit/i})
Here is what Login.test.js looks like
/**
* #jest-environment jsdom
*/
import React from 'react';
import { screen, render, waitFor, fireEvent, getAllByLabelText } from '#testing-library/react';
import {renderHook} from '#testing-library/react-hooks'
import userEvent from '#testing-library/user-event';
import { act } from "react-dom/test-utils";
import Login from "../components/auth/login"
import Index from "../components/index"
import {userContext} from "../utils/context/UserContext"
import axios from 'axios';
const user = {user: {} }
const isLoggedIn = false
const setIsLoggedIn = jest.fn()
const setUser = jest.fn()
const setState = jest.fn();
jest.mock('axios');
describe("Login", () => {
afterEach(() => {
jest.clearAllMocks();
});
test('ajax login', async () => {
const {container} = render(
<userContext.Provider value={ {user, isLoggedIn, setIsLoggedIn, setUser} } >
<Login />
</userContext.Provider>
)
axios.post.mockImplementationOnce(() => Promise.resolve({
status: 200,
data: {user: {name: 'Rony', email: 'rony#example.com' } }
}))
setIsLoggedIn.mockReturnValue(true)
const signIn = jest.spyOn(React, "useState")
signIn.mockImplementation(isLoggedIn => [isLoggedIn, setIsLoggedIn]);
const email = container.querySelector('input[name=email]')
const password = container.querySelector('input[name=password]')
const button = screen.getByRole('button', {name: /submit/i})
await act(async () => {
userEvent.type('email', 'abc#yahoo.com')
userEvent.type('password', 'abcddef')
userEvent.click(button)
});
expect(signIn).toHaveBeenCalledTimes(1)
expect(setIsLoggedIn).toEqual(true);
expect(screen.getByText(/welcome/i)).resolves.toBeInTheDocument
})
})
The login component is shown below:
import React, {useState, useEffect, useContext} from 'react';
import PropTypes from 'prop-types';
import Cookies from 'js-cookie';
import axios from 'axios'
import {userContext} from "../../utils/context/UserContext"
export const Login = (props) => {
const {user, setIsLoggedIn, isLoggedIn, setUser} = useContext(userContext)
const token = Cookies.get('csrf_token') || props.csrf_token || ''
const[loginData, setLoginData] = useState({email: '', password: '' })
const[csrf_token, setCsrfToken] = useState('')
useEffect(() => {
setCsrfToken(token)
}, [])
const handleChange = (event) => {
const {name, value} = event.target
setLoginData({...loginData, [name]: value})
}
const signIn = (event) => {
event.preventDefault();
let headers = {
headers: {'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrf_token
}
}
let url = "/api/v1/sessions"
axios.post(url, {user: loginData}, headers)
.then(response => {
setLoginData({email: '', password: ''})
setIsLoggedIn(true)
setUser(response.data.user)
})
.catch(function (error) {
setIsLoggedIn(false)
})
}
return(
<React.Fragment>
<div> </div>
<form onSubmit={signIn}>
<h3>Login</h3>
<div className="mb-3 form-group col-xs-4">
<label htmlFor="exampleInputEmail1" className="form-label">Email address</label>
<input type="email" name="email" className="form-control" id="exampleInputEmail1"
value={loginData.email} onChange={handleChange} aria-describedby="emailHelp" />
<div id="emailHelp" className="form-text">We'll never share your email with anyone else.</div>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword1" className="form-label">Password</label>
<input type="password" name="password" className="form-control" id="exampleInputPassword1"
value={loginData.password} onChange={handleChange} />
</div>
<div className="mb-3 form-check">
<input type="checkbox" className="form-check-input" id="exampleCheck1" />
<label className="form-check-label" htmlFor="exampleCheck1">Check me out</label>
</div>
<button name="submit" type="submit" className="btn btn-primary" onClick={signIn} >Submit</button>
</form>
</React.Fragment>
)
}
UserContext.js
import React from 'react';
export const userContext = React.createContext(undefined)
The index.js
import React, {useState, useEffect, useContext} from 'react';
import PropTypes from 'prop-types';
import Cookies from 'js-cookie';
import axios from 'axios'
import {userContext} from "../../utils/context/UserContext"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Login from "../components/auth/login";
export default const Index = (props) => {
let form_token = Cookies.get('csrf_token') || ''
let check_login = props.isLoggedIn || false
let check_user = props.user || ""
const[isLoggedIn, setIsLoggedIn] = useState(check_login)
const[user, setUser] = useState(check_user)
const[isError, setIsError] = useState('')
useEffect(() => {
setCsrfToken(form_token)
}, [isLoggedIn, csrf_token, user])
return (
<userContext.Provider value={{user, setUser, isLoggedIn, setIsLoggedIn}}>
<Router>
<React.Fragment>
<div>
<Switch>
<Route exact path='/'>
</Route>
<Route path='/login'>
<Login />
</Route>
</Switch>
</div>
</React.Fragment>
</Router>
</userContext.Provider>
)
}
You should not mock React's useState function, and you cannot test redirection because you are rendering only the Login component in your test.
Beside this I don't thinks you can mock a function defined in a functional component (signIn), and even if it was possible you should not test internal implementation, but only what a real user whould see : here, you can test that the inputs have the correct value after the userEvent.type(...).
Just remove the useState and signIn mocks, then in your Login component add a redirection after a successful login. Then you just have to enter user information, click the submit button (as you already do), then you can check that history.push have been called correctly (see Simplest test for react-router's Link with #testing-library/react).

Infinite loop when working with react and react-firebase-hooks

I am working with a navigation bar that should be able to switch between multiple chat rooms using react and react-firebase-hooks. (https://github.com/CSFrequency/react-firebase-hooks)
However, the chat room will infinitely re-render itself when I choose a room in nav-bar.
I initially thought this is a router issue, but having each rooms sharing the same url, the issue persists.
Right now, when I choose a room using the nav bar, it will send that room number back to App.js using a callback function. App.js will pass that room number to ChatRoom.js, which will get the data from firestore, and re-render itself.
I struggled for several days trying to find anything that could cause the infinite loop with minimal success. Any help would be appreciated!
ChatRoom.js
import React, { useMemo, useRef, useState } from 'react';
import { withRouter } from 'react-router';
import { useCollectionData, useDocument, useDocumentData } from 'react-firebase-hooks/firestore';
import firebase, { firestore, auth } from '../Firebase.js';
import ChatMessage from './ChatMessage';
const ChatRoom2 = (props) => {
console.log("chat room rendered");
function saveQuery(){
const channelid= props.channelid;
const messagesRef = firestore.collection('messages').doc(channelid).collection('chats');
const query = messagesRef.orderBy('createdAt').limitToLast(25);
return [messagesRef,query];
}
var returnedVal = useMemo(()=>saveQuery , [props.channelid]);
const messagesRef = returnedVal[0];
const query = returnedVal[1];
const [messages] = useCollectionData(query, { idField: 'id' });
const [formValue, setFormValue] = useState('');
const sendMessage = async (e) => {
e.preventDefault();
console.log(messagesRef);
console.log(query);
console.log(messages);
const { uid, photoURL } = auth.currentUser;
await messagesRef.add({
text: formValue,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
uid,
photoURL
})
setFormValue('');
}
return (<>
<main>
{messages && messages.map(msg => <ChatMessage key={msg.id} message={msg} />)}
</main>
<form onSubmit={sendMessage}>
<input value={formValue} onChange={(e) => setFormValue(e.target.value)} placeholder="say something nice" />
<button type="submit" disabled={!formValue}>🕊️</button>
</form>
</>)
}
export default ChatRoom2;
ChatList.js (nav bar)
const ChatList = (props) => {
console.log("list rendered");
const query = firestore.collection('users').doc(auth.currentUser.uid).collection('strangers').orderBy('channelID').limitToLast(10);
//console.log(query);
const [channelidArr] = useCollectionData(query, { idField: 'id' });
return (
<div>
{channelidArr && channelidArr.map(channelid =>
<div>
<button onClick={() => props.parentCallback(channelid.channelID)}>{channelid.channelID}</button>
<br />
</div>)}
</div>
);
};
export default ChatList;
App.js
import React, { useRef, useState } from 'react';
import {
BrowserRouter,
Switch,
Route,
Link
} from "react-router-dom";
//import './App.css';
import firebase, { firestore, auth } from './Firebase.js';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useCollectionData } from 'react-firebase-hooks/firestore';
import ChatList from './components/ChatList.js';
import FindNew from './components/FindNew.js';
import Footer from './components/Footer.js';
import Profile from './components/Profile.js';
import ChatRoom2 from './components/ChatRoom2.js';
import SignOut from './components/SignOut.js';
import SignIn from './components/SignIn.js';
import SignUp from './components/SignUp.js';
import ChatRoom from './components/ChatRoom.js';
function App() {
console.log('App rendered');
const [user] = useAuthState(auth);
const [roomNum, setRoomNum] = useState([]);
const callbackFunction = (childData) => {
setRoomNum(childData);
};
return (
<div className="App">
<header>
<h1>⚛️🔥💬</h1>
<SignOut auth={auth} />
</header>
<BrowserRouter >
<Footer />
<Switch>
<Route path="/profile">
<Profile />
</Route>
<Route path="/new">
<FindNew />
</Route>
<Route path="/signup">
{() => {
if (!user) {
return <SignUp />;
} else {
return null;
}
}}
</Route>
<Route path="/direct">
{user ?
<div>
<ChatList parentCallback={callbackFunction} />
<ChatRoom2 channelid={roomNum} />
</div> : <SignIn />}
</Route>
</Switch>
</BrowserRouter>
</div>
);
};
export default App;
Issue Summary
useCollectionData memoizes on the query parameter but since a new query reference was declared each render cycle the firebase hook was rerun and updated collection and rerendered the component.
const { channelid } = props;
const messagesRef = firestore
.collection('messages')
.doc(channelid)
.collection('chats');
const query = messagesRef // <-- new query reference
.orderBy('createdAt')
.limitToLast(25);
const [messages] = useCollectionData(
query, // <-- reference update trigger hook
{ idField: 'id' },
);
Solution
query has only a dependency on the channelid prop value, so we can memoize the query value and pass a stable value reference to the useCollectionData hook.
const { channelid } = props;
const query = useMemo(() => {
const messagesRef = firestore
.collection('messages')
.doc(channelid)
.collection('chats');
const query = messagesRef.orderBy('createdAt').limitToLast(25);
return query;
}, [channelid]);
const [messages] = useCollectionData(
query, // <-- stable reference
{ idField: 'id' },
);

How to redirect to a url along with a component in react such that props passed to the component are not lost

When onClick event is triggered, I want to redirect to a new component (props passed to it) with a new url.
My App.js
import React from "react";
import Main from "./Components/Main/Main";
import "bootstrap/dist/css/bootstrap.min.css";
import styles from "./App.module.css";
import { BrowserRouter as Router, Route} from "react-router-dom";
import SearchBar from "./Components/SearchBar/SearchBar";
import AnimeInfo from "./Components/AnimeInfo/AnimeInfo";
import Cards from "./Components/Cards/Cards"
const App = () => {
return (
<Router>
<div className={styles.container}>
<SearchBar />
<Route path="/" exact component={Main} />
<Route path="/anime/info" component={AnimeInfo} />
<Route path="/anime/cards" component={Cards} />
</div>
</Router>
);
};
export default App;
In the following component, I am passing props to a component but I want to redirect to the url too, but doing so, the props passed that component are lost and I just get redirected
import React, { useEffect, useState } from "react";
import { apiDataTop, apiDataUpcoming, apiDataDay } from "../../api";
import styles from "./TopAnime.module.css";
import AnimeInfo from "../AnimeInfo/AnimeInfo";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
} from "react-router-dom";
const TopAnime = () => {
const [animeData, setAnimeData] = useState([]);
const [animeDataHype, setAnimeDataHype] = useState([]);
const [animeDataDay, setAnimeDataDay] = useState([]);
const [image_url, setImageUrl] = useState("");
useEffect(() => {
callApi();
}, []);
const callApi = async () => {
const results = await apiDataTop();
const hypeResults = await apiDataUpcoming();
const dayResults = await apiDataDay();
setAnimeData(results);
setAnimeDataHype(hypeResults);
setAnimeDataDay(dayResults);
};
console.log(animeDataDay);
return (
<div>
<h1>Recent Release</h1>
<div className={styles.container}>
<br />
{animeDataDay === []
? null
: animeDataDay.map((anime) => {
return (
<a
href
onClick={(event) => {
event.preventDefault();
let animeName = anime.title;
animeName = animeName.replace(/\s+/g, "");
setImageUrl(anime.image_url);
console.log("image url original", anime.image_url);
console.log("image url", image_url);
}}
className={styles.move}
>
<img src={anime.image_url} alt="anime" />
<div className={styles.size}>
<h5>
<b>{anime.title}</b>
</h5>
</div>
</a>
);
})}
{image_url ? (
<Router>
// below commented approch first display the component on the same page and then redirects to the url
// but the props passed are lost !
// <Link to="/anime/info">
// <AnimeInfo image_url={image_url} />
// {window.location.href = `/anime/info`}
// </Link>
<Route
path="/anime/info"
render={() => <AnimeInfo image_url={image_url} />}
/>
</Router>
) : null}
</div>
export default TopAnime;
Following is the component, to whom I want to pass props and use the data passed to display (on a whole new page)!
import React, { useEffect, useState } from "react";
import styles from "./AnimeInfo.module.css";
console.log("The data image props issss", props.image_url);
return (
<div className={styles.container}>
<h1> I am info component</h1>
<img src={props.image_url} alt="anime" />
</div>
);
};
export default AnimeInfo;
Why not use the state property in history.push()?
See it in action here
use the history package.
then create a file at 'src/history.js'
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
then in your component
import history from './history'
history.push({
pathname: '/path',
data_name: dataObject,
});
Then you can access the props in your other component:
this.props.location.data_name
Use render method in router
const renderComponent = (props, Component) => {
// write logic if needed
return <Component {...props} />
}
<Route path="/earner" render={(props) => renderComponent(props, Main)}/>

Categories

Resources