For my first project with React, i tried to use useContext() for call my data mocked.
But after that, i have a problem.
The problem :
You go to a housing page, you change the apartment ID in the URL with a Dummy ID and you update it.
Great! The redirection to the 404 is done.
So you go back to the home page by clicking on the link of the 404.
You go back to any housing page => the 404 is always present.
You repeat, you then return to the home page and you click again on a housing card and WOW the problem no longer exists.
My Provider
import React, { useState, createContext } from "react"
import fetchLocationData from "../../services/localFetch"
export const FetchDataContext = createContext()
export const FetchDataProvider = ({ children }) => {
const [locationData, setLocationData] = useState({})
const [locationsData, setLocationsData] = useState([])
const [allLocationLoading, setAllLocationLoading] = useState(false)
const [isLocationLoading, setIsLocationLoading] = useState(false)
const [errorAPI, setErrorAPI] = useState(false)
const [error404, setError404] = useState(false)
async function fetchLocationById(locId) {
try {
setError404(false)
const response = await fetchLocationData.getLocById(locId)
response ? setLocationData(response) : setError404(true)
} catch (err) {
console.log(err)
setErrorAPI(true)
} finally {
setIsLocationLoading(true)
console.log("in provider :", error404)
}
}
async function fetchAllLocations() {
try {
const response = await fetchLocationData.getAll()
setLocationsData(response)
} catch (err) {
console.log(err)
setErrorAPI(true)
} finally {
setAllLocationLoading(true)
}
}
return (
<FetchDataContext.Provider
value={{
errorAPI,
error404,
isLocationLoading,
allLocationLoading,
locationData,
locationsData,
fetchLocationById,
fetchAllLocations,
}}
>
{children}
</FetchDataContext.Provider>
)
}
My router
function App() {
return (
<>
<GlobalStyle />
<BlocPage>
<Header />
<FetchDataProvider>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/apartment/:locId" element={<ProfileLocation />} />
<Route path="*" element={<Error />} />
</Routes>
</FetchDataProvider>
</BlocPage>
<Footer />
</>
)
}
My housing page
function ProfileLocation() {
const { locId } = useParams()
const {
errorAPI,
error404,
locationData,
isLocationLoading,
fetchLocationById,
} = useContext(FetchDataContext)
useEffect(() => {
fetchLocationById(locId)
}, [])
const rating = parseInt(locationData.rating)
if (errorAPI) {
return (
<span>
Oups une erreur est survenue ... Veuillez recommencer ultérieurement.
</span>
)
}
if (error404) {
return <Navigate to="/error404" />
}
return (
<div>
{isLocationLoading ? (
<>
<Slider pictures={locationData.pictures} />
<ResponsiveWrapper>
<FirstSectionWrapper>
<Title>{locationData.title}</Title>
<Geolocation>{locationData.location}</Geolocation>
<TagsWrapper>
{locationData.tags.map((tag, index) => (
// eslint-disable-next-line react/no-array-index-key
<Tag key={`${tag}-${index}`} label={tag} />
))}
</TagsWrapper>
</FirstSectionWrapper>
<SecondSectionWrapper>
<OwnerWrapper>
<HomeOwnerName>{locationData.host.name}</HomeOwnerName>
<HomeOwerPicture
src={locationData.host.picture}
alt={locationData.host.name}
/>
</OwnerWrapper>
<StarsWrapper>
<StarScale ratingValue={rating} starType="full" />
<StarScale ratingValue={rating} starType="empty" />
</StarsWrapper>
</SecondSectionWrapper>
</ResponsiveWrapper>
<CollapseSectionWrapper>
<CollapseWrapper>
<Collapse
pageType="profil"
label="Description"
contentType="paragraph"
contentText={locationData.description}
/>
</CollapseWrapper>
<CollapseWrapper>
<Collapse
pageType="profil"
label="Équipements"
contentType="list"
contentText={locationData.equipments}
/>
</CollapseWrapper>
</CollapseSectionWrapper>
</>
) : (
<Loader />
)}
</div>
)
}
export default ProfileLocation
Maybe it’s an async code problem?
The issue is that only navigating forward to a route rendering a component that calls fetchLocationById will ever manage to clear/set the error404 state back to false.
I suggest instead of passing error404 back to the components that the FetchDataProvider handles rendering the error UI upon the error condition. Having it redirect to a specific error page.
Example:
<FetchDataProvider>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/apartment/:locId" element={<ProfileLocation />} />
<Route path="/404" element={<Error404 />} /> // <-- add route
<Route path="*" element={<Error />} />
</Routes>
</FetchDataProvider>
...
const Error404 = () => (
<div>Oups une erreur est survenue ... Veuillez recommencer ultérieurement.</div>
);
...
import { useNavigate } from 'react-router-dom';
export const FetchDataProvider = ({ children }) => {
const navigate = useNavigate();
...
async function fetchLocationById(locId) {
try {
setError404(false)
const response = await fetchLocationData.getLocById(locId)
if (response) {
setLocationData(response);
} else {
navigate("/error404", { replace: true });
}
} catch (err) {
navigate("/error404", { replace: true });
} finally {
setIsLocationLoading(true);
}
}
...
return (
...
);
}
...
function ProfileLocation() {
const { locId } = useParams();
const {
locationData,
isLocationLoading,
fetchLocationById,
} = useContext(FetchDataContext);
useEffect(() => {
fetchLocationById(locId);
}, [fetchLocationById, locId]); // <-- don't forget to add proper dependencies
const rating = parseInt(locationData.rating);
return (
<div>
{isLocationLoading ? (
<>
<Slider pictures={locationData.pictures} />
<ResponsiveWrapper>
<FirstSectionWrapper>
<Title>{locationData.title}</Title>
<Geolocation>{locationData.location}</Geolocation>
<TagsWrapper>
{locationData.tags.map((tag, index) => (
// eslint-disable-next-line react/no-array-index-key
<Tag key={`${tag}-${index}`} label={tag} />
))}
</TagsWrapper>
</FirstSectionWrapper>
<SecondSectionWrapper>
<OwnerWrapper>
<HomeOwnerName>{locationData.host.name}</HomeOwnerName>
<HomeOwerPicture
src={locationData.host.picture}
alt={locationData.host.name}
/>
</OwnerWrapper>
<StarsWrapper>
<StarScale ratingValue={rating} starType="full" />
<StarScale ratingValue={rating} starType="empty" />
</StarsWrapper>
</SecondSectionWrapper>
</ResponsiveWrapper>
<CollapseSectionWrapper>
<CollapseWrapper>
<Collapse
pageType="profil"
label="Description"
contentType="paragraph"
contentText={locationData.description}
/>
</CollapseWrapper>
<CollapseWrapper>
<Collapse
pageType="profil"
label="Équipements"
contentType="list"
contentText={locationData.equipments}
/>
</CollapseWrapper>
</CollapseSectionWrapper>
</>
) : (
<Loader />
)}
</div>
);
}
Related
I'm using the material-ui library's component Autocomplete with React flux to create a Search Bar that finds users.
I want every option to be a link to a page with more information of that user. Currently, it only works the first time I click on an option. Every time after that it changes the URL but the page is not re-rendered.
This is the Autocomplete code:
export function SearchBar() {
const [url, setUrl] = useState("/perfil/");
const { store, actions } = useContext(Context);
useEffect(() => {
actions.search();
}, [url]);
const artistas = store.artistas.slice();
return (
<Autocomplete
id="autocomplete"
freeSolo
disableClearable
options={artistas}
getOptionLabel={(option) => option.nombre + " " + option.apellido}
renderOption={(option) => (
<Link
className="text-black text-decoration-none"
onClick={() => setUrl(url + option.id)}
to={`/perfil/${option.id}`}
>
{option.nombre} {option.apellido}
</Link>
)}
renderInput={(params) => (
<TextField
{...params}
size="small"
placeholder="Search for your artist"
margin="normal"
variant="outlined"
InputProps={{ ...params.InputProps, type: "search" }}
/>
)}
/>
);
}
This is the page where it is redirected:
export const Perfil = () => {
const { user_id } = useParams();
return (
<>
<About user_id={user_id} />
<Galeria user_id={user_id} />
</>
);
};
The flux action is this:
search: async () => {
try {
const response = await fetch(
process.env.BACKEND_URL + "/api/artistas"
);
const data = await response.json();
setStore({
artistas: data,
});
} catch (error) {
console.log("Error loading message from /api/artistas", error);
}
}
At last, this is the layout page:
return (
<div>
<BrowserRouter basename={basename}>
<ScrollToTop>
<Navbar />
<Routes>
<Route element={<Inicio />} path="/" />
<Route element={<Login />} path="/login" />
<Route element={<Registro />} path="/registro" />
<Route element={<Perfil />} path="/perfil/:user_id" />
<Route element={<Producto />} path="/producto/:theid" />
<Route
element={<ConfiguracionUsuario />}
path="/configuracion/:theid"
/>
<Route element={<h1> Not found! </h1>} />
</Routes>{" "}
<Footer />
</ScrollToTop>{" "}
</BrowserRouter>{" "}
</div>
);
I fixed it. The problem was in the Galeria component. It took the id by props:
useEffect(() => {
actions.producto_galeria(props.user_id);
}, []);
But we should have used a stored variable with the ID that changes on every click with a flux action, instead of props. So now, I get the ID as follows:
flux new action:
get_artista_id: (artista_id) => {
setStore({ artista_id: artista_id });
}
Link new onClick:
<Link
className="text-black text-decoration-none"
onClick={() => {
actions.get_artista_id(option.id);
}}
to={`/perfil/${option.id}`}
>
{option.nombre} {option.apellido}
</Link>
useEffect on Galeria to detect the change on the ID:
useEffect(() => {
actions.producto_galeria(store.artista_id);
}, [store.artista_id]);
Link tags work first move to url. After that onClick logic will start after moved link.
So if you want to use Link tag with onClick then i recommend two ways.
react-router-dom v5 or lower : just don't use 'to' property and just use onClick and in the onClick property you can use useHistory hook inside onClick.
You can use under 2. Option here too.
react-router-dom v6 or lower : use 'a' tag with onClick property you can use useNavigate hook inside onClick.
When I try to click a song after playing first both the songs play simultaneously instead pausing the first and playing second. Also there is an issue setting song name, stated in comments.
Playing.js
function Playing(props) {
const [song, setSong] = useState('SongName');
const [music, setMusic] = useState();
console.log(props.currentSong.song);
useEffect(() => {
if(typeof props.currentSong.song !== 'undefined') {
setSong(props.currentSong.song.song);
}else{
setSong('SongName'); //Also this Does not work if song state is set to ''
}
if(music){
console.log(music);
music.pause();
}
setMusic(new Audio(require(`./Songs/${song}.mp3`)));
}, [props.currentSong.song, song, setMusic]);
console.log(music);
const player = () => {
if (pauseToggle) {
music.pause();
} else {
music.play();
}
}
return (
<audio></audio>
<i
onClick={player}
className={pauseToggle ? "pause" : "play"}>
</i>
);
}
AllSong.js
function AllSong(props) {
return(
<Link
key={song.image}
to={{
pathname:"/",
state:{songInfo: song}
}}
onClick={() => props.setCurrentSong({song})}
>
<div>{song.song}</div>
</Link>
);
}
App.js
const [currentSong, setCurrentSong] = useState({});
return(
<Switch>
<Route
exact
path="/"
render={props => (
<Playing
{...props}
currentSong={currentSong}
/>
)}
/>
<Route
path="/AllSong"
render={props => (
<AllSong
{...props}
setCurrentSong={setCurrentSong}
/>
)}
/>
</Switch>
);
I also am learning the implementation on hooks so my code must look messy,
I really hope to get an answer from the community.
I have an axios request that allows me to get user information if he's logged and display it on his profile page.
My question is where to call it to avoid too many request and enhance optimization.
I've seen people calling it on top in App.js but I find it weird. I'm currently calling it in my container component that render UserProfile or LogForm depending if user is logged, but then a request is made each time user clicks on his profile page and I don't feel that it is the right way.
Here is my code:
Profile.tsx
const Profile = () => {
const dispatch = useDispatch();
const userId = useSelector((kinaccess: RootStateOrAny) => kinaccess.formsReducer.signInForm.success.userId);
useEffect(() => {
dispatch(Action.getUserInfo(userId));
}, [userId, dispatch]);
return <div>{userId ? <UserProfile /> : <LogForm />}</div>;
};
UserProfile.tsx
const UserProfile = () => {
const userInfo = useSelector((kinnacess: RootStateOrAny) => kinnacess.userReducer.user.success);
if (!userInfo) return null;
const { name, firstName, email } = userInfo;
return (
<section className='user-profile'>
<h3>
Hello {firstName} {name}
</h3>
<p>Is your mail adress still {email}</p>
</section>
);
};
An my Routes.tsx
const Routes = () => {
return (
<Routing>
<Route index element={<Home />} />
<Route path='contact' element={<ContactForm />} />
<Route path='profile' element={<Profile />} />
<Route path='*' element={<Error404 />} />
</Routing>
);
};
I am using "react-scripts": "4.0.2" and all my components are React Hooks. My logic involves nested routing but the end result is not rendered.
App.js:
<BrowserRouter>
<div className="App">
<TopNav />
<Home />
</div>
</BrowserRouter>
Home.js:
<Switch>
<Route path="/" component={Questions} />
</Switch>
Questions.js
const displayQuestion = (qid) => {
props.history.push({ pathname: "/question/" + qid });
};
//questions is an array of objects
const questionsBlocks = questions.map((quest, i) => {
return (
<QBlock
key={i + 1}
qno={i + 1}
displayQuestion={displayQuestion.bind(this, quest.qid)}
/>
);
});
return (
<div>
<h1>Questions</h1>
{questionsBlocks}
<Switch>
<Route
path="/question/:id"
render={(props) => <Question {...props} questions={questions} />}
/>
</Switch>
</div>
);
QBlock will only render buttons that will call displayQuestion on being clicked
QBlock.js:
return (
<div className="block" onClick={props.displayQuestion}>
<h1>{props.qno}</h1>
</div>
);
Question.js:
const [question, setQuestion] = useState();
const loadQuestion = () => {
console.log(props);
if (props.match.params.id) {
console.log("load called");
const qid = props.match.params.id;
const index = props.questions.findIndex((quest) => quest.qid == qid);
setQuestion(props.questions[index]);
}
};
// componentDidMount with react hooks
useEffect(() => {
console.log("Mounted");
loadQuestion();
}, []);
// componentDidUpdate with react hooks
useEffect(() => {
console.log("Updated");
loadQuestion();
}, [props.match.params.id]); //Even tried with only props
return (
<div className="Quest">
<div className="question">{question.question}</div>
<div className="options">{question.answerChoices}</div>
</div>
);
Neither of the useEffect of Question.js is not executing still I am getting the following error.
Basically, question needs to be initialized
const [question, setQuestion] = useState(null);
And another thing you need to do is to check the value of question before using it
return (
<div className="Quest">
{question && question.question && <div className="question">{question.question}</div>}
{question && question.answerChoices && <div className="options">{question.answerChoices}</div>}
</div>
);
As Vince said.. I had defined useState like const [question, setQuestion] = useState(); instead of const [question, setQuestion] = useState({});
Im having problems at the result component i can't render the filteredData, at result this.props.dataToRender ain't working thats the problem.
I have all the logic already, but when trying to display the result of the search on another component i'm getting no information.
If i execute the code on the app component i get the result of the search but if i try to pass it as props i don't get a result at the other side.
state = { data: [], filteredData: [], value: "" };
async componentDidMount() {
axios
.get("https://tenjoucesar.github.io/data.json")
.then(response => response.data)
.then(data => {
this.setState({ data });
console.log(this.state.data);
});
}
handleSubmit(e) {
const { value, data } = this.state;
e.preventDefault();
let email = value;
const filtered = data.filter(e => e.email === email);
this.setState({ filteredData: filtered });
}
handleChange(e) {
this.setState({ value: e.target.value });
}
InfomData = person => {
return (
<div className="result" key={person.email}>
<h3 >
{person.name}, {person.age}
</h3>
<p className="paragraph--result">{person.notes}</p>
</div>
);
};
render() {
const { filteredData } = this.state;
const dataToRender = filteredData.map(e => this.InfomData(e));
return (
<React.Fragment>
<Nav />
<Switch>
<>
<Route exact path="/" component={Header} />
<Search
component={Search}
handleSubmit={this.handleSubmit.bind(this)}
handleChange={this.handleChange.bind(this)}
value={this.props.value}
type={this.props.email}
/>
<Route exact path="/" component={Reverse} />
<Route
path="/result"
component={Result}
dataToRender={dataToRender}
/>
</>
</Switch>
<Footer />
</React.Fragment>
);
}
}
Here at Result component i tried moving informData and the const dataToRender but didn't work.
class Result extends Component {
render() {
return (
<div className="section-result">
<div className="container--result">
<h1 className="heading-primary--result">Results</h1>
<p className="paragraph--results">
Look at the result below to see the details of the person you're
searched for.
</p>
</div>
{this.props.dataToRender}
<TryAgain />
</div>
);
}
}
I just want to get the result on the result component, i had tried with all that i found, nothing worked.
Could someone tell me what do i have to do ? I'm sorry still learning about react
Just Modify the Route to this
<Route
path="/result"
render={() => <Result dataToRender={dataToRender} />}
/>
Since you have to inject props to a component, this is the way
Pass Route's render an arrow function with component and customProps
EDIT
If you also need the history, location, search props that are passed by default when we use <Route path="/" component={Something} /> then pass them explicitly
<Route
path="/result"
render = {(props) => <Result dataToRender={dataToRender} {...props} /> }
/>