I'm studying React and Apollo JS and came across this syntax.
What JS feature is being used where Launches is declared below?
const Launches: React.FC<LaunchesProps> = () => {
const {
data,
loading,
error
} = useQuery<
GetLaunchListTypes.GetLaunchList,
GetLaunchListTypes.GetLaunchListVariables
>(GET_LAUNCHES);
if (loading) return <Loading />;
if (error) return <p>ERROR</p>;
if (!data) return <p>Not found</p>;
return (
<Fragment>
<Header />
{data.launches &&
data.launches.launches &&
data.launches.launches.map((launch: any) => (
<LaunchTile key={launch.id} launch={launch} />
))}
</Fragment>
);
}
That's Typescript not Javascript, and it's a static type annotation.
It types that function as a React Function Component with the type LaunchesProps as the type of the props it receives.
Related
I've been struggling over this error for a while now. It happens when I try to open react-bootstrap Modal with dynamically passed lazy component referrence and props to render it inside. It worked with classic import.
First row points to some react's internal lazy handler:
This is how modals are handled inside my ModalProvider:
const ModalList = React.memo(({ modalList, closeModalByIndex, confirmModalExitByIndex }) =>
modalList.map((modalDef, index) => {
const closeModal = () => closeModalByIndex(index);
const onConfirmExitChange = (confirmExit) => confirmModalExitByIndex(index, confirmExit);
const props = { ...modalDef, key: index, closeModal, onConfirmExitChange };
switch (modalDef.type) {
case TYPE_LIST:
return (
<React.Suspense fallback={fallback}>
<ListModal {...props} />
</React.Suspense>
);
case TYPE_FORM:
return (
<React.Suspense fallback={fallback}>
<FormModal {...props} />
</React.Suspense>
);
case TYPE_LIST_MULTI:
return (
<React.Suspense fallback={fallback}>
<ListMultiModal {...props} />
</React.Suspense>
);
default:
return null;
}
})
);
And this is how it is passed:
const openListModal = (Component, componentProps) => openModal(Component, componentProps, TYPE_LIST);
Anyone with deeper understanding what could possibly cause this?
Found out by trial by error. It was caused by immer's produce function which builds read-only deep copy of object.
setModalList(
produce(modalList, (modalList) => {
modalList.push({ Component, componentProps, type, show: true });
})
);
Summarizing The Problem
- Details About The Goal
A weather app that renders on the screen the data fetched from OpenWeather API.
- Actual and Expected Results
Whether the user types correctly or not the city name or presses enter in the empty field, no result render on the screen. I would like help to resolve it.
What it Has Been Tried So Far (Update 1.1)
I've placed a conditional operator below the *Search* component in the *App.js* file:
{typeof dataSearch === "undefined" ? (<></>) : ()}
{typeof dataSearch === "undefined" ? (
<></>
) : (
<>
<CurrentWeather resultData={weatherData} />
<ForecastWeather resultData={forecastData} />
</>
)}
I would not expect that it would leave the screen blank.
Well, thank you for reading my post.
The Complete Code
- App.js (Update 1.1)
import React, { useState } from "react";
import { Api } from "./Api";
import { Container } from "react-bootstrap";
import {
Search,
CurrentWeather,
ForecastWeather,
Footer,
} from "./components/index";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
function App() {
const [weatherData, setWeatherData] = useState(null);
const [forecastData, setForecastData] = useState(null);
const handleSearchLocation = (dataSearch) => {
const weatherDataFetch = fetch(
`${Api.url}/weather?q=${dataSearch}&units=metric&appid=${Api.key}`
);
const forecastDataFetch = fetch(
`${Api.url}/forecast?q=${dataSearch}&units=metric&appid=${Api.key}`
);
Promise.all([weatherDataFetch, forecastDataFetch]).then(
async (response) => {
const weatherResponse = await response[0].json();
const forecastResponse = await response[1].json();
setWeatherData(weatherResponse);
setForecastData(forecastResponse);
}
);
};
return (
<div className="App">
<div className="contentApp">
<Container>
<Search
searchResultData={handleSearchLocation}
textPlaceholder="Search for a place..."
/>
{typeof dataSearch === "undefined" ? (<></>) : (
<>
<CurrentWeather resultData={weatherData} />
<ForecastWeather resultData={forecastData} />
</>
)}
<Footer />
</Container>
</div>
</div>
);
}
export default App;
Yippee-ki-yay
Summarizing The Problem
The main issue that I was facing was with handling the API logic to display the search result on the screen and before that, once the user types wrong the city name or pressed enter into the blank input field, the program crashed.
However, I've begun to search for why this was happening and in the course of time after observing other codes, I've seen that the IF statement should be used to fix this issue.
Problem Resolution
After several attempts, the solution was to remove the Promise.all() and separate them, weather and forecast, into await blocks of codes with their own IF statement:
// App.js
await weatherDataFetch
.then((res) => {
if (!res.ok) {
throw new Error("City name: typed wrong or blank input.");
}
return res.json();
})
.then((res) => {
setWeatherData(res);
})
.catch((err) => {
console.log(err);
});
await forecastDataFetch
.then((res) => {
if (!res.ok) {
throw new Error(
"Weather forecast not found. Waiting for the correct city name."
);
}
return res.json();
})
.then((res) => {
setForecastData(res);
})
.catch((err) => {
console.log(err);
});
The async has been moved to the top of the handleSearchLocation function:
// App.js
const handleSearchLocation = async (dataSearch) => {
And for the last, deleting the old attempt to clean and avoid conflict and crashes:
// Old
// App.js
return (
<div className="App">
<div className="contentApp">
<Container>
<Search
searchResultData={handleSearchLocation}
textPlaceholder="Search for a place..."
/>
{typeof dataSearch === "undefined" ? (<></>) : (
<>
<CurrentWeather resultData={weatherData} />
<ForecastWeather resultData={forecastData} />
</>
)}
<Footer />
</Container>
</div>
</div>
);
// New
// App.js
return (
<div className="App">
<div className="contentApp">
<Container>
<Search
searchResultData={handleSearchLocation}
textPlaceholder="Search for a place..."
/>
{weatherData && <CurrentWeather resultData={weatherData} />}
{forecastData && <ForecastWeather resultData={forecastData} />}
<Footer />
</Container>
</div>
</div>
);
Note
Other beginners, like me, observe other codes and especially code variations of the same programs because there are a lot of ways to do the same thing.
Yippee-ki-yay
I'm currently rendering two different components based on the value of shouldRenderPlanA - however, despite different components being rendered (depending on the value) - I pass both the same props. How can I condense this to reduce repeated code?
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
shouldRenderPlanA ? (
<StyledLabelOptionOne
variousProps={variousProps}
variousProps={variousProps}
variousProps={variousProps}
/>
) : (
<StyledLabelOptionTwo
variousProps={variousProps}
variousProps={variousProps}
variousProps={variousProps}
/>
)
))}
</StyledRow>
</>
);
To pass same Props to multiple React component or to pass multiple Props to a React component, you can use Object unpacking/destruction within components.
function Component() {
const propPack = {
variousProps1: variousProps1,
variousProps2: variousProps2,
variousProps3: variousProps3,
};
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
shouldRenderPlanA
? <StyledLabelOptionOne {...propPack} />
: <StyledLabelOptionTwo {...propPack} />
))}
</StyledRow>
</>
);
}
This is commonly used to pass all the parent props down to children
function Component(props) {
return (
condition
? <StyledLabelOptionOne {...props} />
: <StyledLabelOptionTwo {...props} />
)
}
You can also conditionally pick the component for rendering (but this IMHO is less readable)
function Component() {
const PickedComponent = shouldRenderPlanA ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
<PickedComponent
variousProps1={variousProps1}
variousProps2={variousProps2}
variousProps3={variousProps3}
/>
))}
</StyledRow>
</>
);
}
For conditions/props derived from within .map() simply move the code within the map callback
function Component() {
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => {
const propPack = {
variousProps1: variousProps1,
variousProps2: opt.value,
};
const PickedComponent = opt.condition ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
shouldRenderPlanA
? <StyledLabelOptionOne {...propPack} />
: <StyledLabelOptionTwo {...propPack} />
)
})}
</StyledRow>
</>
);
}
Note how arrow function within map has becomed an arrow function with a complete block. From (opt) => (first_instruction) to (opt) => { first_instruction; return (second_instruction); }. This allows us to add code before rendering at each map() cycle.
You could assign both options to a variable which contains a union of both component types.
Combining and then spreading the props from an object may also be beneficial, depending on where those props come from. If they are taken from opt inside the map then this second step is probably not required:
const LabelComponent = shouldRenderPlanA ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
<LabelComponent
prop1={opt.prop1}
prop2={opt.prop2}
/>
))}
</StyledRow>
</>
);
You could use the React Context API. This would enable you to share the props across multiple children without passing it to each one of them explicitly.
I am having trouble understanding why I cannot get images to show up in my components. I have a boolean which indicates loading, and an array that gets filled async. When I finish, I set the boolean and the component re renders. Now, I want to create a card for each item in the array and put in in a card deck (this is from react-bootstrap if that wasn't obvious). I can do this with any given boolean and array, but not with the boolean and arrays created with React.useState... Why is that and how should I go about fixing this?
I encountered this problem quite a few hours ago, and have tracked down its source to this minimal working example that still reflects what I am trying to do, but I am unsure of what to do from here.
function TestCard() {
return (
<Card>
<Card.Img src="holder.js/200x200" />
</Card>
);
}
I am trying to render the following component:
function MainComponent() {
const [boolState, setBoolState] = React.useState(false);
const [arrayState, setArrayState] = React.useState([]);
React.useEffect(() => {
setTimeout(() => {
setBoolState(true);
setArrayState([1,2,3]);
}, 2000);
});
return (
<>
{/* This works */}
{
true &&
<CardDeck>
{
[1,2,3].map(_ => {
return (
<TestCard />
);
})
}
</CardDeck>
}
{/* This doesn't, why? */}
{
boolState &&
<CardDeck>
{
arrayState.map(_ => {
return (
<TestCard />
);
})
}
</CardDeck>
}
</>
);
}
Code sandbox
I have a Dashboard component that renders an array of cards with data fetched from a backend server. Users can create additional cards by submitting a form, which then redirects them back to the dashboard page.
My issue is that when the form is submitted, a javascript error 'cannot read property "includes" of undefined' is thrown and the dashboard does not render. If I manually refresh the page, the list renders as expected with the new card. I use Array.includes method to filter the cards based on the filterText state value. Does this error happen because the data has not been fetched when render is called? If so, how can I force the component to wait until there is data before rendering? Please see the components and redux action below.
const CardList = (props) => {
const cards = props.cards.map(({ _id, title}) => {
return (
<Card key={_id} title={title} />
)
});
return (
<div className="container">
<input onChange={ (e) => props.handleChange(e.target.value) } />
<div className="row">
{cards}
</div>
</div>
);
}
export default CardList;
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
filterText: ''
}
}
componentDidMount() {
this.props.fetchCards();
}
handleChange = (filterText) => {
this.setState({filterText});
}
render() {
const cardList = this.props.cards.filter(card =>
card.title.includes(this.state.filterText.trim().toLowerCase())
);
return (
<div>
<CardList cards={cardList}
handleChange={filterText => this.handleChange(filterText)} />
</div>
);
}
};
function mapStateToProps({ cards: { cards }}) {
return {
cards,
}
}
export default connect(mapStateToProps, {fetchCards})(Dashboard);
export class SurveyForm extends Component {
render() {
return (
<div>
<form>
<Field component={CardField} type="text"
label={'title'} name={'title'} key={'title'} />
<Button type="submit" onClick={() => submitCard(formValues, history)}>Next</Button>
</form>
</div>
);
}
}
REDUX ACTION DISPATCHER:
export const submitCard = (values, history) => async dispatch => {
const res = await axios.post('/api/cards', values);
try {
dispatch({ type: SUBMIT_CARD_SUCCESS, payload: res.data });
dispatch({ type: FETCH_USER, payload: res.data })
}
catch(err) {
dispatch({ type: SUBMIT_CARD_ERROR, error: err });
}
history.push('/cards');
}
Similar to what #JasonWarta mentioned, it's worth noting that React does not render anything when false, null, or undefined is returned, so you can usually use && to be more succinct than using the conditional ("ternary") operator:
render() {
return this.props.cards && (
<div>
<CardList
cards={this.props.cards.filter(card => card.title.includes(this.state.filterText.trim().toLowerCase())}
handleChange={filterText => this.handleChange(filterText)}
/>
</div>
);
}
Because && short-circuits, the latter part won't be evaluated so you can avoid TypeErrors, and the component will also render no content (same as when you return null).
I've used ternary operators in this kind of situation. You may need to adjust the check portion of the pattern, depending on what your redux pattern is returning. null value is returned if this.props.cards is falsey.
render() {
return (
{this.props.cards
?
<div>
<CardList
cards={this.props.cards.filter(card => card.title.includes(this.state.filterText.trim().toLowerCase())}
handleChange={filterText => this.handleChange(filterText)}
>
</CardList>
</div>
:
null
}
);
}
As an alternative to other answers you can return something else suitable if there is no data in your render function with an if statement. I prefer moving functions like your filter one outside of render. Maybe one other (better?) approach is doing that filter in your mapStateToProps function.
Also, if I'm not wrong you don't need to pass anything to your handleChange function. Because you are getting filterText back from CardList component then setting your state.
cardList = () => this.props.cards.filter(card =>
card.title.includes(this.state.filterText.trim().toLowerCase()));
render() {
if ( !this.props.cards.length ) {
return <p>No cards</p>
// or return <SpinnerComponent />
}
return (
<div>
<CardList cards={this.cardList()}
handleChange={this.handleChange} />
</div>
);
}