How can I make an api call dynamic with react? - javascript

How can I use the value from the input form and only make an api call when the button is submitted?
Right now when I press submit I am receiving back the user name which is what I expect and want to receive, however it doesn't seem to be sending the information back to userCard. I realize right now I'm not calling it in the form but I'm a bit unsure how to approach this one.
import React,{useEffect, useState} from "react";
import {Form, Button} from "react-bootstrap"
import axios from "axios";
import UserCard from "./Card";
const UserForm = () => {
const[user, setUser] = useState("");
const[login, setLogin] = useState("");
const handleChange = (e) =>{
//console.log(e.target.value)
setUser(e.target.value);
}
const handleSubmit = (e) =>{
//e.preventDefault();
console.log("Button was submitted",user);
axios.get(`https://api.github.com/users/${user}`)
.then((res =>
{setLogin(res.data);
})
)
.catch(err => console.log(err));
}
return (
<div className = "Form">
<Form onSubmit={handleSubmit} onChange = {handleChange} spellcheck="false" >
<Form.Group controlId="formName">
<Form.Label> Github Handle </Form.Label>
<Form.Control type="text" placeholder="Enter Handle Here" />
</Form.Group>
<Button onClick = {handleSubmit}>Submit</Button>
</Form>
</div>
)
}
export default UserForm;
import React from "react"
import { NavLink } from "react-router-dom"
import UserForm from "./UserForm"
const UserCard = (props) =>{
return(
<div className = "Card">
<UserForm />
<h1>Hello, {props.login}</h1>
<h2>How is the weather in {props.location}?</h2>
<h3>Here's a little about you </h3>
<p>{props.bio}</p>
<nav>
<ul className = "cardlist">
<li><NavLink to = "/followers" style={{ textDecoration: 'none' }}>Go To Followers</NavLink></li>
<li><NavLink to = "/repos" style={{ textDecoration: 'none' }}>Go To Repos</NavLink> </li>
</ul>
</nav>
</div>
)
}
export default UserCard;
import './App.css';
import React from "react";
import axios from "axios";
import { BrowserRouter, Link, Route } from 'react-router-dom';
import UserCard from "./components/Card";
import Followers from "./components/Followers";
import Repos from "./components/Repos";
import UserForm from "./components/UserForm"
let followerArray = [];
class App extends React.Component {
state = {
user: '' ,
location : '',
bio: '',
followers: [],
repos: []
}
//make api calls after the component mounts --> equivalent to useEffect()
//componentDidUpdate is equivalent to useEffect(,[])
componentDidMount() {
console.log("Component mounted");
//get user data
console.log("finished")
//get repos
}
render()
{
return (
<BrowserRouter>
<div className="App">
<Route exact path = "/">
<UserCard name = {this.state.name}
login = {this.state.user}
location = {this.state.location}
bio = {this.state.bio}
/>
</Route>
<Route path = "/followers" followers ={this.state.followers}>
<Followers />
</Route>
<Route path = "/repos">
<Repos repos={this.state.repos}/>
</Route>
</div>
</BrowserRouter>
);
}
}
export default App;

When you want to get back some values from child component, you can pass a function to child component. In your case:
Define a Callback function in UserCard component and use the callback function data in card:
import React from "react"
import { NavLink } from "react-router-dom"
import UserForm from "./UserForm"
const UserCard = (props) =>{
const[user, setUser] = useState("");
const[login, setLogin] = useState("");
const giveBackDataToCard = (login , user) => {
setLogin(login);
setUser(user);
}
return(
<div className = "Card">
<UserForm />
<h1>Hello, {login}</h1>
<h2>How is the weather in {user.location}?</h2>
<h3>Here's a little about you </h3>
<p>{user.bio}</p>
<nav>
<ul className = "cardlist">
<li><NavLink to = "/followers" style={{ textDecoration: 'none' }}>Go To Followers</NavLink></li>
<li><NavLink to = "/repos" style={{ textDecoration: 'none' }}>Go To Repos</NavLink> </li>
</ul>
</nav>
</div>
)
}
export default UserCard;
Call props function where you want:
import React,{useEffect, useState} from "react";
import {Form, Button} from "react-bootstrap"
import axios from "axios";
import UserCard from "./Card";
const UserForm = () => {
const handleChange = (e) =>{
setUser(e.target.value);
}
const handleSubmit = (e) =>{
e.preventDefault();
axios.get(`https://api.github.com/users/${user}`)
.then((res =>
{
setLogin(res.data);
//------------> For example here
props.giveBackDataToCard(res.data,e.target.value);
})
)
.catch(err => console.log(err));
}
return (
<div className = "Form">
<Form onSubmit={handleSubmit} onChange={handleChange} spellcheck="false" >
<Form.Group controlId="formName">
<Form.Label> Github Handle </Form.Label>
<Form.Control type="text" placeholder="Enter Handle Here" />
</Form.Group>
<Button onClick = {handleSubmit}>Submit</Button>
</Form>
</div>
)
}
export default UserForm;

Well, If your question is if you want send back the data to the userCard from userForm, You should follow these methods.
First method is, You should learn first a Context APIs in react which already mentioned in docs https://reactjs.org/docs/context.html , use it for passing data between a screen.
Second method is, Always call the API at parent level component, I think its a better and easier way. By calling API from parent level component you will have to pass some props to according to your useForm required.
Third method is, The best way to use some state management store, like Mobx, Redux etc... It is a best way. You can then actually pass your state dynamically to your useCard component.
If answer matched you question, that will great, If not tell so that I will delete for community help.

Related

How to update the value from a custom web component input field into a redux persist local storage

This is my react project it has a input field to get name and pass it to a custom web component.
where the name is displayed also the name can be edited where it has a input field and the value for the input field is taken from local storage which holds persisted state value. In my react project I have used redux toolkit and redux persist to manage and persist the data inside the local storage. This is the code for my react project. Now the problem I am facing is when the data from web component input field is edited and stored in local storage it does not get updated in the redux and when I refresh the persisted data is removed. I App.js I have imported my userprofile.js file which contain custom web component and I used it with this tag in my App.js
App.js:
import "./App.css";
import { Link, Route, Routes } from "react-router-dom";
import "./userprofile.js";
import InputForUserProfile from "./InputForUserProfile";
import { useSelector } from "react-redux";
function App() {
const name = useSelector((state) => state.userProfileDetails.name);
console.log("redux", name);
return (
<div className="App">
<nav>
<ul>
<li>
<Link to="*">InputFields</Link>
</li>
<li>
<Link to="/userprofile">userprofile</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="*" element={<InputForUserProfile />} />
<Route path="/userprofile" element={<user-profile name={name} />} />
</Routes>
</div>
);
}
export default App;
Index.js:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import {persistor, Store} from "./redux/Store";
import { PersistGate } from "redux-persist/integration/react";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store = {Store}>
<PersistGate persistor={persistor}>
<BrowserRouter>
<App />
</BrowserRouter>
</PersistGate>
</Provider>
</React.StrictMode>
);
InputForUserProfile.js:
import { useDispatch } from "react-redux";
import { update } from "./redux/userProfileSlice";
import { useState } from "react";
const InputForUserProfile = () => {
const [inputName, setInputName] = useState('');
const dispatch = useDispatch();
const handleSubmit = (event) => {
event.preventDefault();
dispatch(update({ name: inputName }));
};
return (
<>
<form onSubmit={handleSubmit}>
<label>
Change User Name:
<input
type="text"
placeholder="chance user name"
value={inputName}
onChange={(e) => setInputName(e.target.value)}
/>
</label>
<br />
<button type="submit">Save Changes</button>
</form>
</>
);
};
export default InputForUserProfile;
Store.js and userProfileSlice is stored in a seperate folder named redux.
Store.js:
import {configureStore, } from "#reduxjs/toolkit"
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import thunk from "redux-thunk";
import userProfileReducer from "./userProfileSlice";
const persistConfig = {
key:'root',
storage,
};
const persistedReducer = persistReducer(persistConfig, userProfileReducer)
export const Store = configureStore ({
reducer : {
userProfileDetails:persistedReducer},
middleware: [thunk],
});
export const persistor = persistStore(Store);
userProfileSlice.js:
import { createSlice } from "#reduxjs/toolkit";
export const userProfileSlice = createSlice({
name : "userProfileDetails",
initialState : {
name : '',
},
reducers : {
update : (state, action)=> {
state.name = action.payload.name;
},
},
});
export const {update} = userProfileSlice.actions;
export default userProfileSlice.reducer;
This the code for my custom web component, Here i recive name as prop from above react component.
This component has a input field and the value of the input field is taken from the local storage,
where the persisted value from redux is stored. When I edit value available in input field and click save value is stored in the local storage, but it does not get updated in redux and when I refresh the persisted value becomes empty. How can I solve this?
userprofile.js(custom web components):
class UserProfile extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<div id="profile">
<br /><br />
<img
src=" iii "
alt="Profile Picture"
/>
<h1>
Name:
<p id="name"></p>
</h1>
<input type="text" id="user-name" class="hide-input" placeholder="changeusername">
<button id="save-button" class="hide-input" >save</button>
<button id="edit-button" >Edit Profile</button
><br /><br />
</div>
`;
}
connectedCallback() {
const userVaule = this.shadowRoot.querySelector("div");
this.shadowRoot.querySelector('#name').textContent = this.getAttribute("name");
userVaule
.querySelector("#save-button")
.addEventListener("click", this.saveProfile.bind(this));
userVaule
.querySelector("#edit-button")
.addEventListener("click", this.editProfile.bind(this));
userVaule.querySelectorAll("input, #save-button").forEach((el) => {
el.classList.add("hide-input");
});
}
editProfile() {
editProfile() {
this.shadowRoot.querySelectorAll("input, #save-button").forEach((el) => {
el.classList.remove("hide-input");
});
let data = localStorage.getItem("persist:root");
if (!data) return;
let parsedData = JSON.parse(data);
console.log('2',parsedData);
let name = parsedData.name;
console.log("1", name);
this.shadowRoot.querySelector("#user-name").value = name;
}
saveProfile() {
this.shadowRoot.querySelectorAll("input, #save-button").forEach((el) => {
el.classList.add("hide-input");
});
let name = this.shadowRoot.querySelector("#name");
const userName = this.shadowRoot.querySelector("#user-name").value;
let data = localStorage.getItem("persist:root");
let parsedData = JSON.parse(data);
parsedData.name = userName;
localStorage.setItem("persist:root", JSON.stringify(parsedData));
name.textContent = userName;
}
}
customElements.define("user-profile", UserProfile);
Since the web component is created with pure java script I don't know how to update the value back into redux.

React hook component renders before API call

I need to create a React app which let's you list pokemons and types.
I fetch data from the PokeAPI. Is it a good practice to fetch it from the App component and then pass it to the child components, or is it better to fetch them from the child?
I am fetching it in the main app, I can see the fetch works because I console.log the data, but my component doesn't get it, and because of that I get a props.map is not a function in .
Here is my App.js:
import React, { useState } from "react";
import logo from "./logo.svg";
import "./App.css";
import axios from "axios";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import PokemonList from "./components/PokemonList";
const App = (props) => {
const [pokemons, setPokemons] = useState([]);
const [types, setTypes] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const getPokemons = () => {
const axios = require("axios").default;
axios.get("https://pokeapi.co/api/v2/pokemon").then(function (response) {
console.log("Fetched pokemons");
console.log(response.data.results);
setIsLoading(false);
setPokemons(response.data.results);
});
};
const getTypes = () => {
setIsLoading(true);
const axios = require("axios").default;
axios.get("https://pokeapi.co/api/v2/type").then(function (response) {
console.log("Fetched types");
console.log(response.data.results);
setIsLoading(false);
setTypes(response.data.results);
});
};
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/pokemons" onClick={getPokemons}>
Pokemons
</Link>
</li>
<li>
<Link to="/types">Types</Link>
</li>
</ul>
</nav>
{/* A <Switch> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<Switch>
<Route path="/pokemons">
<Pokemons pokemons={pokemons} />
</Route>
<Route path="/types">
<Types />
</Route>
</Switch>
</div>
</Router>
);
};
function Pokemons(pokemons) {
return <PokemonList props={pokemons} />;
}
function Types(typeList) {
return <h2>TYPES:</h2>;
}
export default App;
Here is my PokemonList.js:
import React from "react";
import { Card } from "semantic-ui-react";
import PokeCard from "./PokeCard";
const Pokemonlist = (props) => {
let content = (
<Card.Group>
{props.map(function (object, i) {
return <PokeCard pokemon={object} key={i} />;
})}
</Card.Group>
);
return content;
};
export default Pokemonlist;
and last here is my PokeCard.js
import { Card, Image } from "semantic-ui-react";
import React from "react";
const PokeCard = (pokemon) => {
let content = (
<Card>
<Card.Content>
<Image floated="right" size="mini" src={pokemon.img} />
<Card.Header>{pokemon.name}</Card.Header>
<Card.Meta>{pokemon.base_experience}</Card.Meta>
<Card.Description>ID: {pokemon.id}</Card.Description>
</Card.Content>
</Card>
);
return content;
};
export default PokeCard;
So the basic idea is:
On the main page you click Pokemons button, which calls the fetch then renders the PokemonList component which basically just renders multiple PokeCard components from the data I fetched.
1, What am I missing here?
2, In my situation when nothing changes do I need to use useEffect?
3, When should I fetch the data, and where?
EDIT: I want to use hooks with zero classes
here is a summary of my answer
it is best to fetch some initial data in parent and then make further requests in child
component if necessary to save network usage
use the useEffect hook to fetch the results before rendering the elements
What you are missing is that you are not using props in pokemon and you should put the get call inside useEffect hook in App component because the child component is rendering before the props is passed to it and this is the reason you are getting undefined error

How to implement onClick componentDidMount() in React using Axios

I am trying to make a movie search function using React, Axios, and movieDB API. The functionality I am trying to implement right now is typing in a movie into the search bar and clicking the submit button will return the movie title as an H1 element.
My onClick function does not work: <button onClick={(e)=>clickHandler()}>submit</button>
componentDidMount() will work only when the page refreshes and you cannot search for anything as the submit button is broken.
I am not sure how to implement this, but I would also not mind if I could get it to search by hitting enter instead of using a button, whichever is easier.
Here is my code so far.
App.js
import React from "react"
import Movielist from './components/Movielist'
function App() {
return (
<div>
<input type="search" id="search" />
<button onClick={(e)=>clickHandler()}>submit</button>
<h1 id="title">title</h1>
<Movielist />
</div>
)
}
export default App
Movielist.js
import React from 'react';
import axios from 'axios';
export default class Movielist extends React.Component {
state = {
title: ""
}
componentDidMount() {
const API_KEY = '***********************';
const query = document.getElementById('search').value;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
const title = res.data['results'][0]['title'];
this.setState({ title });
})
}
render() {
return (
<h1>{this.state.title}</h1>
)
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
import React from 'react';
import axios from 'axios';
export default class Movielist extends React.Component {
state = {
title: ""
}
clickHandler = (event) => {
if (event.keyCode === 13) {
const query = event.target.value;
const API_KEY = '***********************';
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
const title = res.data['results'][0]['title'];
this.setState({ title });
})
}
}
render() {
return (
<input type="search" id="search" onKeyDown={event => this.clickHandler(event)} />
<h1>{this.state.title}</h1>
)
}
}
You should call API to get the movie list after hitting the button, then pass the data that you've got to Movielist. Try this:
In App.js:
import React from "react"
import axios from 'axios'
import Movielist from './components/Movielist'
function App() {
const [movieList, setMovieList] = React.useState([])
const handleOnSubmit = () => {
const API_KEY = '***********************';
const query = document.getElementById('search').value;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
const title = res.data['results'][0]['title'];
setMovieList(res.data['results'])
})
}
return (
<div>
<input type="search" id="search" />
<button onClick={handleOnSubmit}>submit</button>
<h1 id="title">title</h1>
<Movielist movieList={movieList}/>
</div>
)
}
export default App
In Movielist.js:
import React from 'react';
const Movielist = ({movieList}) => {
return (
<div>
{
movieList.map(movie => <h1 key={movie.key}>{movie.title}</h1>)
}
<div/>
)
}
}
export default Movielist
import React, {useState} from "react"
import axios from 'axios';
import Movielist from './components/Movielist'
const [title, setTitle] = useState("")
const API_KEY = '***********************'
function App() {
const clickHandler = () => {
const query = document.getElementById('search').value;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
const title = res.data['results'][0]['title'];
setTitle(title);
})
}
return (
<div>
<input type="search" id="search" />
<button onClick={clickHandler}>submit</button>
<h1 id="title">title</h1>
<Movielist title={title} />
</div>
)
}
export default App
just move call api handle to your onclik func then pass title props to movie list
If you want to query the API after user push submit button. You should put your call to API in the call handler, then pass the state from App to MovieList as props
export class App extends React.Component {
state = {
title: ""
}
clickHandler() {
const API_KEY = '***********************';
const query = document.getElementById('search').value;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`).then(res => {
const title = res.data['results'][0]['title'];
this.setState({ title });
});
}
render() {
return (
<div>
<input type="search" id="search" />
<button onClick={(e)=>clickHandler()}>submit</button>
<h1 id="title">title</h1>
<Movielist list={this.state.title}/>
</div>
)
}
}
export class MovieList extends React.Component {
render() {
<h1>{this.props.title}</h1>
}
}
Alternatively, you can wrap the input in a element and use onSubmit + evt.preventDefault() instead, by doing so you can handle button click and pressing "Enter" to submit.

Posting State to firebase

I am trying to post name from state to firebase, and keep getting status 405.
I have tried changing how i import and send the data, but I cannot figure out where I am going wrong.
Index.js:
import React, { Component, Fragment, useState } from "react";
import { render } from "react-dom";
import axios from "axios";
import firebase from "firebase";
import { firebaseConfig } from "./firebase";
import Header from "./components/Header";
import "./style.css";
const App = () => {
const [name, setName] = useState("Ryan");
const handleClick = e => {
console.log("Working");
axios.post(
"https://lq-time-tracking.firebaseio.com/",
{ name },
{ firebaseConfig }
);
};
return (
<div>
<Header name={name} handleClick={handleClick} setName={setName} />
</div>
);
};
render(<App />, document.getElementById("root"));
Header.js:
import React from "react";
import styled from "styled-components";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Home from "./Home";
import "../style.css";
const Header = ({ name, handleClick, setName }) => {
return (
<Router>
<nav className="navbar">
<Link className="nav-item" to="/contact">
Contact
</Link>
<Link className="nav-item" to="/about">
About
</Link>
<Link className="nav-item" to="/home">
Home
</Link>
</nav>
<Switch>
<Route
exact
path="/home"
render={(...props) => (
<Home name={name} handleClick={handleClick} setName={setName} />
)}
/>
</Switch>
</Router>
);
};
export default Header;
Home.js:
import React, { Fragment } from "react";
const Home = ({ name, setName, handleClick }) => {
return (
<>
<h1>This is my State: {name}</h1>
<input type="text" onChange={e => setName(e.target.value)} />
<button type="Submit" onClick={e => handleClick(e)}>
Submit
</button>
</>
);
};
export default Home;
If I am not mistaking, since you use https://lq-time-tracking.firebaseio.com (which is a Firebase Realtime Database URL) I understand that you are trying to write the value name to the Realtime Database by performing a POST request to the https://lq-time-tracking.firebaseio.com URL.
This will not work because, as explained in the doc, while you can use the Firebase Realtime Database URL as a REST endpoint, you "need to append .json to the end of the URL".
In addition, in your case, I think you should use a PUT since you just want to write a string to your Firebase database.
It is not clear in your question, where you want to write the data in the database, but if you want to write the value name to the name subnode of the users/user1 node, you would do as follows:
axios.put("https://lq-time-tracking.firebaseio.com/users/user1.json", {name})

Spy on onClick for Button - React-redux

I'm trying to get this form working for the first time and would just like to know that my onclick is at least working. I'd like to inject a spy to replace the handler that my dispatchToProps is referencing as well.
So in other words I'd like to replace this:
AsyncActions.login
with loginSpy
I can't just do button.props().login = loginSpy because props are immutable at that point. I get TypeError: Can't add property login, object is not extensible
So is there a way to use restructuring through an ES6 class, specifically an ES6 react component via its constructor or something like that?
I know you can do {prop1, prop2} as a parameter in a stateless function, for example:
function FieldGroup({ id, label, help, ...props }) {
but what about ES6 classes in React?
Test
it.only('can log in successfully', async () => {
const container = shallow(<LoginContainer store={store} />),
loginContainer = shallow(<LoginContainer store={store} />),
login = loginContainer.dive().find(Login),
loginForm = login.dive().find(LoginForm),
loginFormLogin = await loginForm.props().login(),
button = loginForm.dive().find('.ft-login-button'),
loginSpy = spy()
button.props().login = loginSpy
button.simulate('click')
expect(loginSpy.calledOnce).to.be.true
})
Container
import { connect } from 'react-redux'
import React, { Component } from 'react'
import * as AsyncActions from '../actions/User/UserAsyncActions'
import Login from '../components/Login/Login'
class LoginContainer extends Component {
componentWillMount(){
// const requested = this.user.requested
}
render(){
return( <Login login={this.props.login} /> )
}
}
const mapStateToProps = state => {
return {
requesting: state.user.requesting,
token: state.user.token,
session: state.user.session
}
}
export const mapDispatchToProps = {
login: AsyncActions.login
}
export { Login }
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
LoginForm
import React, { Component } from 'react'
import { Button, FormControl, FormGroup, ControlLabel, PageHeader } from 'react-bootstrap'
class LoginForm extends Component {
render(){
return (
<div className='ft-login-form'>
<PageHeader className='ft-header'>Login</PageHeader>
<form>
<FormGroup controlId="formBasicText" >
<ControlLabel>Email</ControlLabel>
<FormControl
bsSize="small"
className="ft-username"
componentClass="input"
placeholder="Enter mail"
style={{ width: 300}}
type="text"
/>
<ControlLabel>Password</ControlLabel>
<FormControl
bsSize="small"
className="ft-password"
componentClass="input"
placeholder="Enter Password"
style={{ width: 300}}
type="text"
/>
</FormGroup>
<Button
className='ft-login-button'
onClick={this.props.login}
type='submit'>Login</Button>
</form>
</div>)
}
}
export default LoginForm
You should shallow render LoginForm instead of LoginContainer and simply pass loginSpy as a prop to LoginForm to test the button...
it.only('can log in successfully', async () => {
const loginSpy = spy(),
loginForm = shallow(<LoginForm login={loginSpy} />),
button = loginForm.dive().find('.ft-login-button')
button.simulate('click')
expect(loginSpy.calledOnce).to.be.true
})

Categories