I'm trying to send a parameter to the api to give me an exact json response, but when i'm trying to submit my form via select onChange={onAddSubmit}, it is still passing the default value 146846 but I already changed my select to 146847 so i'm getting the invalid response.
Home.js
import React, { useEffect, useState } from "react";
import Api from "../Api";
import AppContainer from "../tmp/AppContainer";
import HomeContainer from "./HomeContainer";
const Home = () => {
const [posts, setPosts] = useState();
const [loading, setLoading] = useState(false);
const [allValues, setAllValues] = useState({
contractId: "146846",
planId: "1028",
dateStart: "2021-01-30",
dateEnd: "2021-01-31",
numberOfAdults: 1,
numberOfChildren: 0,
planOption: "Individual",
unit: "day",
});
const changeHandler = (e) => {
setAllValues({ ...allValues, [e.target.name]: e.target.value });
};
const onAddSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
await Api.getCurlPost({
...allValues,
}).then((response) => {
const result = response.data;
setPosts(result.data);
});
} catch {
alert("Failed to add post!");
} finally {
setLoading(false);
}
};
useEffect(() => {
onAddSubmit;
}, []);
return (
<AppContainer>
<HomeContainer
onAddSubmit={onAddSubmit}
changeHandler={changeHandler}
loading={loading}
posts={posts}
/>
</AppContainer>
);
};
export default Home;
Here is my HomeContainer.js that contains my html and bootstrap design
import React, { Component } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faPlane } from "#fortawesome/free-solid-svg-icons";
import { faCircleNotch } from "#fortawesome/free-solid-svg-icons";
class HomeContainer extends Component {
render() {
const { onAddSubmit, changeHandler, loading, posts } = this.props;
return (
<div>
<div className="row mb-auto">
<div className="col-md-6 p-2">
<img
width="100%"
src="../resources/img/starr/7437.jpg"
/>
<div className="mt-3 shadow-sm p-3 mb-5 bg-white rounded">
<h3 className="text-center">
<FontAwesomeIcon
icon={faPlane}
rotation={0}
style={{ marginRight: 5 }}
/>
Travel Details
</h3>
<form onChange={onAddSubmit}>
<div className="form-group">
<select
className="form-control"
name="contractId"
onChange={changeHandler}
>
<option value="146846">
TravelLead Domestic Travel Insurance
</option>
<option value="146847">
TravelLead International Travel
Insurance
</option>
</select>
</div>
<div className="form-group">
<select
className="form-control"
name="planId"
onChange={changeHandler}
>
<option value="1028">
Economy (Single Trip)
</option>
<option value="1029">
Elite (Single Trip)
</option>
</select>
</div>
</form>
</div>
</div>
</div>
</div>
);
}
}
export default HomeContainer;
I figured it already, I just added allValues as a parameter when calling onAddSubmit on my useEffect hook, and if there is something changes happen to allValues list it will automatically submit a request to the API and throw a response.
useEffect(() => {
onAddSubmit(allValues);
}, [allValues]);
And onAddSubmit function, just change the ...allValues to ...data on api parameter that sends a request to axios.
const onAddSubmit = async (data) => {
setLoading(true);
try {
await Api.getCurlPost({
...data,
}).then((response) => {
const result = response.data;
setPosts(result.data);
});
} catch {
alert("Failed to add post!");
} finally {
setLoading(false);
}
};
Related
Currently I'm working on feedback app. Trying to edit feedback and send the updated one to the firebase. Unfortunatelly nothing got updated and it throws an error
Can't find a place where is the indexOf used and for sure it's not in feedbackSlice.js.
It happens only when I want to edit the feedback. It shows the data from firebase just fine except for this particular moment when it's time to update doc in firebase.
I'm pretty new to Redux to be honest and really can't find where the problem may be...
EditFeedbackPage.js
import React, { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { selectOpenFeedback, editFeedback } from '../features/feedbacks/feedbackSlice';
import styled from 'styled-components';
import NavbarFeedback from '../components/NavbarFeedback';
import { db } from '../firebase';
import { doc, setDoc } from 'firebase/firestore';
const EditFeedbackPage = () => {
const dispatch = useDispatch()
const selectedFeedback = useSelector(selectOpenFeedback);
const [titleFeedback, setTitleFeedback] = useState(selectedFeedback?.title);
const [categoryFeedback, setCategoryFeedback] = useState(selectedFeedback?.category)
const [detailFeedback, setDetailFeedback] = useState(selectedFeedback?.detail);
const [statusFeedback, setStatusFeedback] = useState(selectedFeedback?.status);
const cancelEditFeedback = () => {
alert('Feedback has been CANCELED!');
}
const sendEditFeedback = (e) => {
e.preventDefault();
const docRef = doc(db, 'feedbacks', selectedFeedback.id)
setDoc(docRef, dispatch(editFeedback({
...selectedFeedback,
title: titleFeedback,
category: categoryFeedback,
detail: detailFeedback,
status: statusFeedback,
}))).then(docRef => {
console.log("Document has been updated successfully")
}).catch(error => {
console.log(error)
})
}
const deleteFeedback = () => {
alert('Feedback has been DELETE!');
}
return (
<EditFeedbackContainer>
<EditFeedbackWholeContainer>
<NavbarFeedback />
<EditFeedbackInnerContainer>
<h2>Create Edit Feedback</h2>
<EditFeedbackFormContainer>
<h4>Feedback Title</h4>
<label htmlFor="headline">Add a short, descriptive headline</label>
<input
type="text"
name='headline'
value={titleFeedback}
onChange={(e) => setTitleFeedback(e.target.value)}
/>
<h4>Category</h4>
<label htmlFor="categories">Choose a category for your feedback</label>
<select
value={categoryFeedback}
name="categories"
id="categories"
onChange={(e) => setCategoryFeedback(e.target.value)}
>
<option value="All">All</option>
<option value="UI">UI</option>
<option value="UX">UX</option>
<option value="Enhancement">Enhancement</option>
<option value="Bug">Bug</option>
<option value="Feature">Feature</option>
</select>
<h4>Update Status</h4>
<label htmlFor="status">Change feedback status</label>
<select
value={statusFeedback}
name="status"
id="status"
onChange={(e) => setStatusFeedback(e.target.value)}
>
<option value="Suggestion">Suggestion</option>
<option value="Planned">Planned</option>
<option value="In-Progress">In-Progress</option>
<option value="Live">Live</option>
</select>
<h4>Feedback Detail</h4>
<label htmlFor="details">Include any specific comments on what should be improved, added, etc.</label>
<textarea
name="details"
id="details"
value={detailFeedback}
onChange={(e) => setDetailFeedback(e.target.value)}
/>
<EditFeedbackButtonsContainer>
<EditFeedbackButtonDelete onClick={deleteFeedback}>Delete</EditFeedbackButtonDelete>
<EditFeedbackButtonCancel onClick={cancelEditFeedback}>Cancel</EditFeedbackButtonCancel>
<EditFeedbackButtonAdd onClick={sendEditFeedback}>Edit Feedback</EditFeedbackButtonAdd>
</EditFeedbackButtonsContainer>
</EditFeedbackFormContainer>
</EditFeedbackInnerContainer>
</EditFeedbackWholeContainer>
</EditFeedbackContainer>
)
}
feedbackSlice.js
import { createSlice } from "#reduxjs/toolkit";
export const feedbackSlice = createSlice({
name: 'feedback',
initialState: {
selectedFeedback: null,
},
reducers: {
selectFeedback: (state, action) => {
state.selectedFeedback = action.payload;
},
editFeedback: (state, action) => {
if (state.selectedFeedback.id === action.payload.id) {
state.selectedFeedback = action.payload;
}
},
},
})
export const { selectFeedback, editFeedback } = feedbackSlice.actions;
export const selectOpenFeedback = (state) => state.feedback.selectedFeedback;
export const selectEditedFeedback = (state) => state.feedback.selectedFeedback;
export default feedbackSlice.reducer;
SingleThreadPage.js
import ExpandLessIcon from '#mui/icons-material/ExpandLess';
import ChatBubbleIcon from '#mui/icons-material/ChatBubble';
import styled from 'styled-components';
import NavigateBeforeIcon from '#mui/icons-material/NavigateBefore';
import { selectOpenFeedback } from '../features/feedbacks/feedbackSlice';
import { SidebarOption } from '../components/Sidebar';
import AddCommentSection from '../components/AddCommentSection';
import Comment from '../components/Comment';
const SingleThreadPage = () => {
const history = useNavigate();
const selectedFeedback = useSelector(selectOpenFeedback);
return (
<SingleThreadPageContainer>
<SingleThreadPageContainerWhole>
<ThreadNav>
<ThreadNavLeft onClick={() => history('/')}>
<NavigateBeforeIcon />
<p>Go Back</p>
</ThreadNavLeft>
<ThreadNavRight>
<button onClick={() => history('/edit')}>Edit Feedback</button>
</ThreadNavRight>
</ThreadNav >
<ThreadContainer>
<ThreadLeft>
<ThreadVotes>
<ExpandLessIcon />
<h4>{selectedFeedback.upVotesCount}</h4>
</ThreadVotes>
<ThreadContent>
<ThreadInfo>
<h3>{selectedFeedback.title}</h3>
<p>{selectedFeedback.detail}</p>
</ThreadInfo>
<ThreadCategory>
<SidebarOption>{selectedFeedback.category}</SidebarOption>
</ThreadCategory>
</ThreadContent>
</ThreadLeft>
<ThreadRight>
<ThreadComments>
<ChatBubbleIcon />
<h3>{selectedFeedback.commentsCount}</h3>
</ThreadComments>
</ThreadRight>
</ThreadContainer>
<Comment />
<AddCommentSection />
</SingleThreadPageContainerWhole>
</ SingleThreadPageContainer>
)
}
export default SingleThreadPage;
store.js
import { configureStore } from '#reduxjs/toolkit';
import feedbackReducer from '../features/feedbacks/feedbackSlice';
import userReducer from '../features/users/userSlice';
export const store = configureStore({
reducer: {
feedback: feedbackReducer,
user: userReducer,
},
});
I ma creating app to get pokemons, first I save them in a list and after that show them, but I am getting empty card and after that the same pokemn is showing even if I am searching for another one.
Also I am getting distortion view when I use my component
import { useState, useEffect } from "react";
import "./App.css";
import { v4 } from "uuid";
import Button from "./Components/Button";
import Input from "./Components/Input";
import Label from "./Components/Label";
import Card from "./Components/Card";
import axios from "axios";
function App() {
// const [textFromInput, setTextFromInput] = useState("");
const [name, setName] = useState("");
const [nameFromButtonClick, setNameFromButtonClick] = useState("");
const [pokemon, setPokemon] = useState({
name: "",
picture: "",
id: 0,
type1: "",
type2: "",
});
const [list, setList] = useState([]);
const handleChange = (event) => setName(event.target.value);
const handleClick = () => {
setNameFromButtonClick(name);
setList([...list, pokemon]);
};
// const handleClick = () => setList([...list, pokemon]);
useEffect(() => {
axios
.get(`https://pokeapi.co/api/v2/pokemon/${name}/`)
.then((res) => {
console.log(res);
// setPokemon(res.data);
console.log("res.data=>", res.data);
setPokemon({
name: res.data.name,
picture: res.data.sprites.front_default,
id: res.data.id,
type1: res.data.types[0].type.name,
type2: res.data.types[1].type.name,
});
})
.catch((err) => {
console.log(err);
});
}, [nameFromButtonClick]);
return (
<div className="App">
<div>
<h1>Pokémon Effect</h1>
</div>
<div className="centered">
<div className="container">
{list.map((entry) => (
<Card key ={v4()}
name={entry.name}
picture={entry.picture}
id={entry.id}
type1={entry.type1}
type1={entry.type2}
/>
))}
</div>
<div className="dashboard">
<Input className="input" value={name} onChange={handleChange} />
<Button
className="getPokemon"
text="GetPokemon"
onClick={handleClick}
/>
<Label text={name} />
</div>
</div>
</div>
);
}
export default App;
this is my component Card, I don't know how to make it look like when I ma writing directly in app.js
export default function Card(props){
const{name, picture,id,type1,type2}=props
return(
<div className="card">
<div><img src={picture} alt={name} /></div>
<p>n:{id}</p>
<div> name={name}</div>
<div className="pokeType">
<div className="classType">type={type1}</div>
<div className="classType">type={type2}</div>
</div>
</div>
)
}
I am trying to make my App.js route to my People.jsx etc.. but it is not working correctly. I hope I could fix the issue from there if I could make this work. I have been trying to do this for about 2 hours with the 20 min rule but this one I need help with. I have tried other variations but my goal is to get the,theID over to Person as well. I am thinking about using {useContext } to do that but I can't even get it to route. I wish I knew what I was doing wrong so I could correct it but other people are using different types of routers and I was confused with them even more.
I updated it with links still a no go for me any other suggestions?
App.js
import './App.css';
import { useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import People from './components/People'
import Planet from './components/Planets'
import Starship from './components/Starships'
import { Router, Link } from '#reach/router';
function App() {
const [starwarsState, setStarwarsState] = useState('')
const [theID, setTheID] = useState('')
const selectedState = (e) => {
setStarwarsState(e.target.value)
}
const switchItem = () => {
switch (starwarsState) {
case 'people':
<Link path='/people/' />;
break;
case 'planets':
<Link path="/planets/" />;
break;
case 'starships':
<Link path='/starships/' />;
break;
default:
return null;
}
}
const addId = e => {
setTheID(e.target.value)
console.log(theID)
}
return (
<div className='App'>
<header className='App-header' >
Search For:
<select onChange={selectedState} className='form-control-lg bg-dark text-white'>
<option value='people' active >People</option>
<option value='planets' >Planets</option>
<option value='starships' >Starships</option>
</select>
ID:
<input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
<button className='btn-lg btn-warning' onClick={switchItem} >Search Item</button>
<Router>
<People path='/people/' />
<Planet path="/planets/" />
<Starship path='/starships/' />
</Router>
</header>
{starwarsState}
</div>
)
}
export default App;
People.jsx
import React, { useState, useEffect } from 'react'
import axios from 'axios'
import { Link } from '#reach/router';
const People = props => {
const [peopleData, setpeopleData] = useState([]);
useEffect(() => {
axios.get(`https://swapi.dev/api/people/${props.theID}`)
.then(response => { setpeopleData(response.data) })
console.log(peopleData)
}, []);
return (
<div>
<span> the People have spoken</span>
<Link to='/people' />
</div>
)
}
export default People;
Issues
You aren't actually rendering the routes/links from switchItem since onClick callbacks can't return renderable UI directly to the render method.
Solution
Unconditionally render your routes all at the same time within a single Router and imperatively navigate to them in the switchItem handler.
App
...
import { Router, navigate } from "#reach/router";
...
function App() {
const [starwarsState, setStarwarsState] = useState("");
const [theID, setTheID] = useState("");
...
const switchItem = () => {
switch (starwarsState) {
case "people":
navigate("/people"); // <-- imperative navigation
break;
case "planets":
navigate("/planets");
break;
case "starships":
navigate("/starships");
break;
default:
return null;
}
};
return (
<div className="App">
<header className="App-header">
Search For:
<select
onChange={selectedState}
value={starwarsState}
className="form-control-lg bg-dark text-white"
>
<option disabled value="">
Choose Path
</option>
<option value="people">
People
</option>
<option value="planets">Planets</option>
<option value="starships">Starships</option>
</select>
ID:
<input
type="text"
onChange={addId}
className="form-control-lg col-sm-1 bg-dark text-white"
/>
<button className="btn-lg btn-warning" onClick={switchItem}>
Search Item
</button>
</header>
<Router>
<People path="/people" theID={theID} /> // <-- pass `theID` state as prop
<Planet path="/planets" />
<Starship path='/starships' />
</Router>
</div>
);
}
People
const People = ({ theID }) => {
const [peopleData, setpeopleData] = useState([]);
useEffect(() => {
axios.get(`https://swapi.dev/api/people/${theID}`)
.then(response => { setpeopleData(response.data) });
}, [theID]);
return (
<div>
<div>The ID: {theID}</div>
<span>the People have spoken</span>
</div>
);
};
Use Imperative Routing (not switch statement) with Event Handlers
Your code is using a switch statement in combination with the switchItem() function. This is not how to redirect the user imperatively (meaning, through something other than a link clicked directly by the user).
To imperatively route your users, use the navigate method.
Via Reach Router docs (link):
Sometimes you need to navigate in response to something other than the user clicking on a link. For this we have navigate. Let’s import navigate.
import {
Router,
Link,
navigate
} from "#reach/router"
In your case, the entire switch statement can be rewritten as follows:
useEffect(() => navigate(`/${starwarsState}`), [starwarsState])
useEffect will watch for changes to the starwarsState, which is either going to be 'people', 'planets', or 'starships'. Once a change occurs, it will navigate the user to the corresponding path.
Solution: Routing Only
The following solution doesn't implement axios, it focuses solely on the client-side routing logic.
I found some other issues with your code when I was working through a solution. Here is a version that I wrote that implements parameter-level routing while also making some other cleanup (refactoring the swapi categories into a config object, etc).
App.js
import React, { useState, useEffect } from 'react'
import { useWhatChanged } from "#simbathesailor/use-what-changed";
import { Router, Link, navigate } from "#reach/router";
import 'bootstrap/dist/css/bootstrap.min.css';
import { People, Person } from './components/People'
import { Planets, Planet } from './components/Planets'
import { Starships, Starship } from './components/Starships'
import './App.css';
function App() {
// destructure categories from config
const { people, planets, starships } = config.categories
// initialize state
const [starwarsState, setStarwarsState] = useState(people);
const [theID, setTheID] = useState('');
// log updates to ID and starwarsState
useWhatChanged([starwarsState, theID], 'starwarsState, theID')
// change state on input from user
const addId = e => setTheID(e.target.value)
const selectCategory = (e) => setStarwarsState(e.target.value)
// route the user based on starwarsState
useEffect(() => navigate(`/${starwarsState}`), [starwarsState])
// search swapi based on category and id
const searchSwapi = e => {
e.preventDefault()
navigate(`/${starwarsState}/${theID}`)
}
return (
<div className="App">
<header className='App-header' >
Search For:
<form onSubmit={searchSwapi}>
<select onChange={selectCategory} className='form-control-lg bg-dark text-white'>
<option value={people} >People</option>
<option value={planets} >Planets</option>
<option value={starships} >Starships</option>
</select>
ID:
<input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
<button className='btn-lg btn-warning' >Search Item</button>
</form>
</header>
<Router>
<People path='/people/'>
<Person path=':personId' />
</People>
<Planets path="/planets/">
<Planet path=':planetId' />
</Planets>
<Starships path='/starships/'>
<Starship path=':starshipId' />
</Starships>
</Router>
</div>
)
}
const config = {
categories: {
people: 'people',
planets: 'planets',
starships: 'starships'
}
}
export default App;
Planets.js
import React from 'react'
export const Planets = props => {
return (
<div>
<span> the Planets have spoken</span>
{props.children}
</div>
)
}
export const Planet = props => {
return (
<div>
Planet Data
</div>
)
}
People.js
import React, { useState, useEffect } from 'react'
export const People = props => {
return (
<div>
<span> the People have spoken</span>
{props.children}
</div>
)
}
export const Person = props => {
return (
<div>
Person Data
</div>
)
}
Starships.js
import React from 'react'
export const Starships = props => {
return (
<div>
<span> the Starships have spoken</span>
{props.children}
</div>
)
}
export const Starship = props => {
return (
<div>
Starship Data
</div>
)
}
[UPDATE] Solution: Routing with API Calls
The following solution takes the code from above and refactors it using the state management pattern proposed by Leigh Halliday. The solution adds three things:
useContext for managing global state
React.memo() for memoizing AppContent component
react-query for managing remote API calls to SWAPI.
View code on GitHub
App.js
// App.js
import React, {
useState,
useEffect,
createContext,
useContext,
memo
} from 'react'
import { ReactQueryDevtools } from "react-query-devtools";
import { useWhatChanged } from "#simbathesailor/use-what-changed";
import { Router, navigate } from "#reach/router";
import 'bootstrap/dist/css/bootstrap.min.css';
import { People, Person } from './components/People'
import { Planets, Planet } from './components/Planets'
import { Starships, Starship } from './components/Starships'
import './App.css';
import Axios from 'axios';
// APP w/ CONTEXT PROVIDER
export default function App() {
return (
<>
<StarwarsProvider>
<AppContent />
</StarwarsProvider>
<ReactQueryDevtools initialIsOpen={false} />
</>
)
}
// CREATE CONTEXT
export const StarwarsContext = createContext()
// CONTEXT PROVIDER
function StarwarsProvider({ children }) {
// import categories
const categories = config.categories
// destructure default category of search selection
const { people } = categories
// initialize state
const [category, setCategory] = useState(people);
const [theID, setTheID] = useState('');
return (
<StarwarsContext.Provider value={{
category,
setCategory,
theID,
setTheID,
categories,
fetchStarwarsData
}}>
<AppContent />
</StarwarsContext.Provider>
)
}
async function fetchStarwarsData(category, id) {
if (!id) {
return
}
const response = await Axios.get(
`https://swapi.dev/api/${category}/${id}`
).then(res => res.data)
// const data = await response.json()
const data = response
// console.log(data)
return data
}
// APP CONTENT
const AppContent = memo(() => {
// import global state into component
const { category, setCategory } = useContext(StarwarsContext)
const { theID, setTheID } = useContext(StarwarsContext)
// destructure categories
const { categories: { people, planets, starships } } = useContext(StarwarsContext)
// log updates to ID and category
useWhatChanged([category, theID], 'category, theID')
// change state on input from user
const addId = e => setTheID(e.target.value)
const selectCategory = (e) => setCategory(e.target.value)
// route the user based on category
useEffect(() => navigate(`/${category}`), [category])
// search swapi based on category and id
const searchSwapi = e => {
e.preventDefault()
navigate(`/${category}/${theID}`)
}
return (
<div className="App">
<header className='App-header' >
Search For:
<form onSubmit={searchSwapi}>
<select onChange={selectCategory} className='form-control-lg bg-dark text-white'>
<option value={people} >People</option>
<option value={planets} >Planets</option>
<option value={starships} >Starships</option>
</select>
ID:
<input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
<button className='btn-lg btn-warning' >Search Item</button>
</form>
</header>
<Router>
<People path='/people/'>
<Person path=':personId' fetchStarwarsData />
</People>
<Planets path="/planets/">
<Planet path=':planetId' fetchStarwarsData />
</Planets>
<Starships path='/starships/'>
<Starship path=':starshipId' fetchStarwarsData />
</Starships>
</Router>
</div>
)
})
const config = {
categories: {
people: 'people',
planets: 'planets',
starships: 'starships'
}
}
People.js
// People.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"
export const People = props => {
return (
<div>
<span> the People have spoken</span>
{props.children}
</div>
)
}
export const Person = () => {
const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)
const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)
if (isLoading) return <div>loading...</div>
if (error) return <div>oop!! error ocurred</div>
return (
<div>
<h1>/{category}/{theID}</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
Planets.js
// Planets.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"
export const Planets = props => {
return (
<div>
<span> the Planets have spoken</span>
{props.children}
</div>
)
}
export const Planet = props => {
const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)
const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)
if (isLoading) return <div>loading...</div>
if (error) return <div>oop!! error ocurred</div>
return (
<div>
<h1>/{category}/{theID}</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
Starships.js
// Starships.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"
export const Starships = props => {
return (
<div>
<span> the Starships have spoken</span>
{props.children}
</div>
)
}
export const Starship = () => {
const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)
const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)
if (isLoading) return <div>loading...</div>
if (error) return <div>oop!! error ocurred</div>
return (
<div>
<h1>/{category}/{theID}</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
when i add course i can see that in AllCourses list and in redux but when i refresh the the Allcourse list is empty and redux is empty too i think there is some thing wrong in courses.ja/actions. what am I doing wrong?
courses.js/actions
i think data in not fetching in reducer to store thre is sme thing wrong with the code or something else
import {coursesRef} from '../services/fire';
const FETCH_COURSES = 'FETCH_COURSES';
export const addCourse = newCourse => async dispatch => {
coursesRef.push().set(newCourse);
};
export const removeCourse = removeCourse => async dispatch => {
coursesRef.child(removeCourse).remove();
};
export const fetchCourse = () => async dispatch => {
coursesRef.on("value", snapshot => {
dispatch({
type: FETCH_COURSES,
payload: snapshot.val()
});
});
};
AddCourse.js
import React, { useEffect, useState } from 'react';
import { Button, Form, FormGroup, Label, Input, FormText, Container } from 'reactstrap';
import database from '../services/fire';
import { useSelector, useDispatch } from 'react-redux';
import uuid from 'react-uuid';
import '../App.css';
const AddCourse = () => {
const [courseId, setCourseId] = useState('');
const [courseTitle, setCourseTitle] = useState('');
const [courseDesc, setCourseDesc] = useState('');
const dispatch = useDispatch();
const user = useSelector(state => state.auth.user.uid);
useEffect(() => {
document.title = "Add Courses"
}, [])
const addCourse = () => {
const payload = { id: uuid(), courseId:courseId, courseTitle: courseTitle, courseDesc: courseDesc }
const dbcoursesWrapper = database.ref().child(user).child('courses');
// const dbcoursesWrapper = database.ref(`users/${user}/courses`).push(courseId, courseTitle, setCourseDesc);
return dbcoursesWrapper.child(payload.id).update(payload).then(() => {
setCourseId('');
setCourseTitle('');
setCourseDesc('');
dispatch({ type: "ADD_COURSES", payload });
})
}
return (
<div>
<h1 className="text-center my-3">Fill Course Detail</h1>
<Form onSubmit={(e) => {
e.preventDefault(e.target.value);
addCourse();
}}>
<FormGroup>
<label for="UserId">Course Id</label>
<Input
type="text"
value={courseId}
onChange={(e) => setCourseId(e.target.value)}
placeholder="Enter your Id"
name="userId"
id="UserId"
/>
</FormGroup>
<FormGroup>
<label for="title">Course Title</label>
<Input
type="text"
value={courseTitle}
onChange={(e)=> setCourseTitle(e.target.value)}
placeholder="Enter Course Title"
name="title"
id="title"
/>
</FormGroup>
<label for="description">Course Description</label>
<Input
value={courseDesc}
onChange={(e) => setCourseDesc(e.target.value)}
type="textarea"
placeholder="Enter Course Description"
name="description"
id="description"
style={{ height: 150 }}
/>
<Container className="text-center">
<Button color="success" type='submit'>Add Course</Button>
<Button color="warning ml-3">clear</Button>
</Container>
</Form>
</div>
);
};
export default AddCourse;
AllCourses.js code
onst AllCourses = () => {
const dispatch = useDispatch();
const courses = useSelector(state => state.courses);
const coursesArray = Object.values(courses);
useEffect(()=>{
console.log(coursesArray);
},[])
return (
<div>
<h1>All-Courses</h1>
<p>List Of Couses are as follows</p>
{coursesArray.length}
{ coursesArray.length > 0 ? coursesArray.map((item) =>
<Course course={item} />) : "No Courses"
}
</div>
)
}
export default AllCourses;
you need to call fetchCourse in AllCourses.js
useEffect(() => {
fetchCourse();
}, [])
So whenever you are on all courses component, or you refresh page you will get the courses.
make a
fetchCourse
function in action so can call it in different places for delete course view course etc
action.js
export const fetchCourse = (user) => async dispatch => {
const coursesRef = database.ref().child(user).child('courses');
coursesRef.once('value')
.then((snapshot) => {
const courses = [];
snapshot.forEach((childSnapshot) => {
courses.push({
id: childSnapshot.key,
...childSnapshot.val()
});
});
// dispatch(setcourses(courses));
dispatch({ type: "FETCH_COURSES", courses });
});
};
reducer corses.js
case 'FETCH_COURSES':
return action.courses
allCourses.js
useEffect(()=>{
dispatch(fetchCourse(user));
},[])
I have the current web page below and want to delete a user when I click on the red 'x' button at the top of the card.
Currently, after I click the 'x' delete button, nothing happens. After I refresh the page, the user will be removed from my webpage. But I want this to happen without needing a refresh at all.
Sample Web Page Render:
Back-end Route:
To achieve this, I tried the following:
Setup back-end route:
const router = require('express').Router()
const {User} = require('../db/models')
module.exports = router
const {isUser, isAdmin} = require('../checks')
// 8080/api/users/:userId
router.delete('/:userId', isAdmin, async (req, res, next) => {
let userId = req.params.userId
try {
await User.destroy({
where: {
id: userId
}
})
res.status(204)
} catch (err) {
next(err)
}
})
Setup redux store and thunk:
import axios from 'axios'
// ACTION TYPES
const SET_USERS = 'SET_USERS'
const DELETE_USER = 'DELETE_USER'
// ACTION CREATORS
export const setUsers = users => ({
type: SET_USERS,
users: users
})
export const deleteUser = delUserId => ({
type: DELETE_USER,
delUserId: delUserId
})
// DOLLAR HELPER FOR CENTS FIELD
// export const toDollars = cents => {
// return `$${(cents / 100).toFixed(2)}`
// }
// THUNK CREATORS
export const getUsers = () => async dispatch => {
try {
const {data} = await axios.get('/api/users')
dispatch(setUsers(data))
console.log('getUsersThunk DATA ARRAY', data)
} catch (err) {
console.error(err)
}
}
export const deleteUserThunk = delUserId => async dispatch => {
try {
const response = await axios.delete(`/api/users/${delUserId}`)
const deleteUserId = response.data
dispatch(deleteUser(deleteUserId))
console.log('getUsersThunk DELETE', deleteUserId)
} catch (err) {
console.error(err)
}
}
// REDUCER
// INITIAL STATE
const allUsers = []
export default function(state = allUsers, action) {
switch (action.type) {
case SET_USERS:
return action.users
case DELETE_USER: {
let userRemovalArray = state.filter(user => user.id !== action.delUserId)
return userRemovalArray
}
default:
return state
}
}
Build front-end component that calls 'deleteUserThunk'
import React from 'react'
import {connect} from 'react-redux'
import {getUsers, deleteUserThunk} from '../store/allUsers'
import {updateUserThunk, fetchSingleUser} from '../store/singleUser'
// Status Filter import BeerFilter from './BeerFilter'
import Card from 'react-bootstrap/Card'
import Button from 'react-bootstrap/Button'
import {UncontrolledCollapse} from 'reactstrap'
export class AllUsers extends React.Component {
constructor(props) {
super(props)
this.state = {
showForm: false,
stat: ''
}
this.clickHandlerOne = this.clickHandlerOne.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
componentDidMount() {
try {
this.props.fetchInitialUsers()
} catch (error) {
console.error(error)
}
}
clickHandlerOne() {
let hidden = this.state.showForm
this.setState({
showForm: !hidden
})
}
handleChange(event) {
//console.log('event.target', event.target)
this.setState({
[event.target.name]: event.target.value
})
}
async handleSubmit(userId) {
event.preventDefault()
const updatedUser = {
id: userId,
isAdmin: this.state.stat
}
// console.log('UPDATE USER', updatedUser)
await this.props.updateUserThunk(updatedUser)
this.props.fetchInitialUsers()
}
render() {
const users = this.props.users
// console.log('PROPS', this.props)
console.log('USERS', this.props.users)
return (
<div>
{/* <div className="options">
<select onChange={this.handleChange}>
<option value="">Sort By...</option>
<option value="priceHighToLow">Price (high to low)</option>
<option value="priceLowToHigh">Price (low to high)</option>
<option value="name">Name</option>
</select>
<BeerFilter />
</div> */}
<div className="flex-cards">
{users.map(user => (
<Card style={{width: '18rem'}} key={user.id}>
{/* delete thunk */}
<div>
<Button
id={`delete${user.id}`}
variant="danger"
onClick={() => this.props.deleteUserThunk(user.id)}
>
X
</Button>
</div>
<Card.Body>
<Card.Title>User Id: {user.id}</Card.Title>
<Card.Text>
<div>
<ul>
<li>
<div className="highlight">
<img src={user.imageUrl} />
</div>
<div className="details">
<p>Username: {user.username}</p>
<p>User Email: {user.email}</p>
<p>Admin Status: {user.isAdmin ? 'true' : 'false'}</p>
<p>
Created Date:{' '}
{new Intl.DateTimeFormat('en-GB', {
month: 'short',
day: '2-digit',
year: 'numeric'
}).format(new Date(user.createdAt))}
</p>
<p />
<Button
id={`user${user.id}`}
onClick={() => {
this.clickHandlerOne()
}}
variant="outline-info"
>
Admin Status Toggle
</Button>
<UncontrolledCollapse toggler={`#user${user.id}`}>
<form onSubmit={() => this.handleSubmit(user.id)}>
<div>
<span>
<select
name="stat"
value={this.state.isAdmin}
onChange={this.handleChange}
>
<option value="">user isAdmin?</option>
<option value="true">true</option>
<option value="false">false</option>
</select>
</span>
<div>
{/* */}
<button type="submit">Submit</button>
</div>
</div>
</form>
</UncontrolledCollapse>
</div>
</li>
</ul>
</div>
</Card.Text>
</Card.Body>
</Card>
))}
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
users: state.allUsers
}
}
const mapDispatchToProps = dispatch => {
return {
loadSingleUser: id => dispatch(fetchSingleUser(id)),
updateUserThunk: updatedUser => dispatch(updateUserThunk(updatedUser)),
//getSortedBeers: (sortBy, beers) => dispatch(sortBeers(sortBy, beers)),
fetchInitialUsers: () => dispatch(getUsers()),
deleteUserThunk: userId => dispatch(deleteUserThunk(userId))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AllUsers)
With my code above, when I click on the red 'x' button nothing happens. I have to hit the refresh button for my now deleted user to be removed from my webpage.
How can I have the user removed from my current view without having to hit refresh?
This isn't a complete answer, but there's definitely a problem here:
const response = await axios.delete(`/api/users/${delUserId}`)
If you look at the screenshot you provided of the error in the web console, it's showing undefined where delUserId should be. So somewhere along the line between the click on the 'X' and the line above, you aren't passing the user ID correctly.
Here, as you are using mapDispatchToProps in your component "AllUsers"
your deleteUserThunk(in THUNK CREATORS) is assigned to deleteUserThunk(in your component "AllUsers")
hence, you need to call your THUNK CREATEORS function by calling the component function which is assigned in mapDispatchToProps
You have to call it in the following way
onClick={() => this.props.deleteUserThunk(user.id)}
This will pass your user.id to deleteUserThunk(in your component "AllUsers") to deleteUserThunk(in THUNK CREATORS)
As per your comments..
Firstly, you have to remove it from componentDidMount() because, you should not run your delete function when your component is mounted.
Secondly, if your reducer is updated, then your browser will update without any refresh. Try checking the parameters which are passed into DELETE_USER
my suggestion would be:
in your function deleteUserThunk (in THUNK CREATORS)
replace dispatch(deleteUser(deleteUserId)) with
dispatch(deleteUser(delUserId))