Can't display the output of a search on another component - reactjs

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} /> }
/>

Related

Question about useContext and re-rendering

Considering a situation where I have an App component, and Contexts components, called 'AuthContext' and 'usersAndProductsContext'.
In the context components I have few states, which are initialized in App component and from there will be given to other components to share the states like so:
return (
<div className="App">
<usersAndProductsContext.Provider
value={{
usersVal: usersVal,
productsVal: productsVal,
toggle_pressed_login_flag: toggle_pressed_login_flag,
handleAddUser: handleAddUser,
}}
>
<AuthContext.Provider
value={{
isLoggedIn: isLoggedIn,
edit_currentLoggedUserId: edit_currentLoggedUserId,
currentLoggedUserId: currentLoggedUserId,
isLoggedInEditVal: isLoggedInEditVal,
}}
>
<Routes>
<Route exact path="/" element={<Login />} />
<Route exact path="farmers" element={<Farmers />} />
<Route exact path="customer" element={<Customer />} />
<Route exact path="register" element={<Register />} />
<Route exact path="farmershop/:id" element={<Farmer />} />
<Route exact path="mystore/" element={<MyStore />} />
</Routes>
</AuthContext.Provider>
</usersAndProductsContext.Provider>
</div>
);
when I pass to other components, as I did using 'Provider', it means I'm sharing with all other components these states which are given within the 'value' prop.
Does that mean that every change of the passed-on-using-provider states, in other component, will mean that the information which was passed on in all components has now changed?
Or did it become some sort of local copy?
To emphasize what I mean:
function MyStore() {
let authCtx = useContext(AuthContext);
let usersAndItemsCtx = useContext(usersAndProductsContext);
...
if I change usersAndItemsCtx from MyStore, will it mean that all other component's state that I have changed?
Hope it made sense?
Regards!
It depends on what you mean by change. If you mean mutating the state then no, but if you mean update the state then yes.
If you do authCtx.someProperty = false;, then do not expect the other components to rerender. The actual way to update the context would by passing a setter function down the context and using it.
Looking at this, there seems to be a handleAddUser, if this is called and it updates the context only then a change will reflected in other components.
value={{
usersVal: usersVal,
productsVal: productsVal,
toggle_pressed_login_flag: toggle_pressed_login_flag,
handleAddUser: handleAddUser,
}}
Here is a sandbox where I have mutated the context. Note, how the setInterval logs the mutated value but there is no update to the UI because the component never rerendered. These kind of bugs are common when mutating state (or context by extension).
The relevant code:
const TextContext = createContext();
function App() {
const [text, setText] = useState({
val: "string",
count: 0
});
return (
<>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<TextContext.Provider value={[text, setText]}>
<Wrapper />
</TextContext.Provider>
</div>
</>
);
}
function Wrapper(props) {
return (
<div style={{ padding: "20px" }}>
<TextControlWithHook />
<br />
<Random />
</div>
);
}
function Random() {
const [text, setText] = useContext(TextContext);
const mutateContext = () => {
text.val = "1231232";
};
return <button onClick={mutateContext}>MUTATE</button>;
}
function TextControlWithHook(props) {
const [text, setText] = useContext(TextContext);
useEffect(() => {
console.log("outside", { text });
const id = setInterval(() => {
console.log(text);
}, 5000);
return () => clearInterval(id);
}, [text]);
return (
<input
className="form-control"
value={text.val}
onChange={(e) => {
const val = e.target.value;
setText((text) => ({ ...text, val }));
}}
/>
);
}

React : Cycle life problem expected / redirect problem

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>
);
}

How to set a new song over current playing song react

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.

Where to call http requests regarding optimization in React

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>
);
};

history.push is doing an extra redirect, and ignoring the search parameters, if the user is already on the redirect page

I have a simple Navbar with a Form within it:
const NavBar = () => {
let history = useHistory()
...
...
return (
...
<Form inline onSubmit={handleSubmit}>
<InputGroup style={{width: "90%"}}>
<Form.Control id="navbar-search" placeholder="Pesquise" size="sm"/>
<Form.Control as="select" size="sm">
<option>Ações</option>
<option>Declarações</option>
</Form.Control>
<InputGroup.Append size="sm">
<Button size="sm" type="submit" variant="outline-secondary">
<SearchIcon fontSize="small" />
</Button>
</InputGroup.Append>
</InputGroup>
</Form>
...
)
handleSubit is suppose to redirect (using history.push) to a path that I'm going to use the input value as a search parameter. I'm using react-router-dom.
const handleSubmit = (e) => {
let baseEndpoint = e.target[1].value === "Ações" ? "actions" : "quotes"
history.push({
pathname: `/${baseEndpoint}/query`,
search: `?description=${e.target[0].value}`,
})
}
Everything looks good except if the user is in the page the handleSubmit is going to redirect, ie, at /${baseEndpoint}/query.
If the user is in this page, history goes to /${baseEndpoint}/query?description=${e.target[0].value} and re-renders automatically to /${baseEndpoint}/query?.
I've also tried using history.replace, but it didn't work.
const handleSubmit = (e) => {
let baseEndpoint = e.target[1].value === "Ações" ? "actions" : "quotes"
let url = `/${baseEndpoint}/query`
if(history.location.pathname === url) {
history.replace({
pathname: url,
search: `?description=${e.target[0].value}`,
})
return
}
history.push({
pathname: url,
search: `?description=${e.target[0].value}`,
})
}
What is causing this behaviour? What am I doing wrong?
Thanks a lot!
(EDIT) My Switch and Routes:
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
class Wrapper extends Component {
render() {
return (
<>
<Container className="main">
<MetaData />
<Navbar />
<this.props.component {...this.props} />
<br />
<br />
<Footer />
</Container>
</>
)
}
}
export default function App() {
return (
<Router>
<Switch>
<Route exact path="/"
render={props => (
<Wrapper {...props} component={Home} />
)}
/>
<Route
exact path="/actions"
key="action-home"
render={props => (
<Wrapper
{...props}
image={bozoAction}
baseEndpoint="actions"
component={EntityHome}
/>
)}
/>
<Route
path="/actions/query"
key="action-query"
render={props => (
<Wrapper
{...props}
image={bozoAction}
baseEndpoint="actions"
component={EntityQuery}
/>
)}
/>
<Route
path="/actions/:id(\d+)"
key="action-entity"
render={props => (
<Wrapper
{...props}
image={bozoAction}
baseEndpoint="actions"
component={Entity}
/>
)}
/>
<Route
exact path="/quotes"
key="quote-home"
render={props => (
<Wrapper
{...props}
image={bozoQuote}
baseEndpoint="quotes"
component={EntityHome}
/>
)}
/>
<Route
path="/quotes/query"
key="quote-query"
render={props => (
<Wrapper
{...props}
image={bozoQuote}
baseEndpoint="quotes"
component={EntityQuery}
/>
)}
/>
<Route
path="/quotes/:id(\d+)"
key="quote-entity"
render={props => (
<Wrapper
{...props}
image={bozoQuote}
baseEndpoint="quotes"
component={Entity}
/>
)}
/>
...
</Switch>
</Router>
)
}
Sandbox: https://codesandbox.io/s/0y8cm
Form some reason the sandbox is not redirecting and ignoring the search parameters, but we can evaluate the error doing the following: use the navbar to search one of the entities. Check the form (clicking in "Mais filtros") and see the provided query param at the input. Do another search in the navbar to the same entity. Check the form and see no initial values.
What's causing the behavior is the form submission event, it refreshes the page resulting in the loss of the query params, the solution to this is e.preventDefault() when you submit the form :
const handleSubmit = (e) => {
e.preventDefault(); // prevent the page refresh
let baseEndpoint = e.target[1].value === "Ações" ? "actions" : "quotes";
history.push({
pathname: `/${baseEndpoint}/query`,
search: `?description=${e.target[0].value}`
});
};
You can add history.go() if you absolutely have to refresh the page, but i wouldn't recommend that, the goal is to let the child components know that the search has changed,
The way your EntityQuery and QueryForm is set up makes it impossible for the child components to know if there is a change, so you need to fix that,
You shouldn't set the initial state value from the props in the constructor as it's anti-pattern , instead, have the initial values empty and use life cycle methods to update them ( componentDidMount and componentDidUpdate )
EntityQuery
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: true,
hasMore: false,
searchParams: {
page: 1,
tags: "",
description: ""
},
entities: []
}
}
updateStateValues = () => {
const _initialTag = this.props.location.search.includes("tags")
? this.props.location.search.split("?tags=")[1]
: "";
const _initialText = this.props.location.search.includes("description")
? this.props.location.search.split("?description=")[1]
: "";
this.setState({
searchParams: {
page: 1,
tags: _initialTag,
description: _initialText
}
})
}
componentDidMount() {
this.updateStateValues()
}
componentDidUpdate(prevProps) {
// add checks for other potential props that you need to update if they change
if (prevProps.location.search.split("?description=")[1] !== this.props.location.search.split("?description=")[1])
this.updateStateValues()
}
And pass the values from the state to the child :
<QueryForm
baseEndpoint={this.props.baseEndpoint}
tagsInitialValues={this.state.searchParams.tags}
textInitialValue={this.state.searchParams.description}
setSearchParams={this.setSearchParams}
/>
Updated CodePen
EDIT:
Fllowing up the OP's comment and the updated SandBox, there's another issue regarding Formik where the initialValues do not update when the props change, see : https://github.com/formium/formik/issues/811
The suggested solution of adding enableReinitialize didn't work, so, to force the component to update, you can use a key that changes when the url changes, in this case, use this.props.textInitialValue :
QueryForm :
<Formik
key={this.props.textInitialValue}
...
Try adding e.prevenDefault() at the start of the handleSubmit function.
Maybe <form> submission is causing a page reload and hence, removing the search parameters.
Try to prevent the default behavior of the form on submission by changing your handleSubmit() handler to:
const handleSubmit = (e) => {
e.preventDefault()
let baseEndpoint = e.target[1].value === "Ações" ? "actions" : "quotes"
history.push({
pathname: `/${baseEndpoint}/query`,
search: `?description=${e.target[0].value}`,
})
}
Let me if it works *

Resources