Related
Hello I'm trying to display the content of an object on my html page using react. I managed to access the frontend property that are in my object array. Now when I try to browse each element of my frontend object array and display the id of each element on my page it displays only the first element which is "HTML". But I want to display all the other id on the page. I don't know what is the problem
The external data
const skills = [
{
frontend: [
{
id: 'HTML',
img: './images/skills/html5.svg'
},
{
id: 'CSS',
img: './images/skills/css3.svg'
},
{
id: 'Javascript',
img: './images/skills/javascript.svg'
},
{
id: 'Bootstrap',
img: './images/skills/bootstrap.svg'
},
{
id: 'React JS',
img: './images/skills/react.svg'
},
{
id: 'Flutter',
img: './images/skills/flutter.svg'
},
],
backend: [
{
id: 'PHP',
img: './images/skills/php.svg'
},
{
id: '.NET Core',
img: './images/skills/net-core.svg'
},
{
id: 'Node JS',
img: './images/skills/html5.svg'
},
],
languages: [
{
id: 'Java',
img: './images/skills/java.svg'
},
{
id: 'Python',
img: './images/skills/python.svg'
},
{
id: 'C#',
img: './images/skills/csharp.svg'
},
],
},
];
export default skills;
My home file
import React, { useEffect, useState } from "react";
import "../styles/Home.css";
import Skills from "../data/Skills";
export const Home = () => {
const [skills, setSkills] = useState([]);
useEffect(() => {
setSkills(Skills);
}, []);
const frontend = skills.map((element) => {
return element.frontend;
});
console.log(frontend);
return (
<div className="home">
<div className="skills">
<h1>Skills</h1>
{frontend.map((item, index) => {
console.log(index)
return <ul key={index}>
<li>{item[index].id}</li>
</ul>;
})}
</div>
</div>
);
};
The result is here
If you are already importing data, you don't need to store that into a state,
Try this ,
import React, { useEffect, useState } from "react";
import "./styles.css";
import Skills from "./Skills";
export const Home = () => {
let frontend = [];
Skills.forEach((skill) => {
frontend = [...frontend, ...skill.frontend];
});
return (
<div className="home">
<div className="skills">
<h1>Skills</h1>
{frontend.map((item, index) => {
return (
<ul key={item.id}>
<li>{item.id}</li>
</ul>
);
})}
</div>
</div>
);
};
Try this,
Change data file to
const skills = {
frontend: [...],
backend: [...],
languages: [...],
};
export default skills;
Code
import React, { useEffect, useState } from "react";
import "../styles/Home.css";
import Skills from "../data/Skills";
export const Home = () => {
return (
<div className="home">
<div className="skills">
<h1>Skills</h1>
{Skills.frontend.map(item => <ul key={item.id}>
<li>{item.img}</li>
</ul>}
</div>
</div>
);
};
Or as I understand what you are trying to do, try this
import React, { useEffect, useState } from "react";
import "../styles/Home.css";
import Skills from "../data/Skills";
export const Home = () => {
return (
<div className="home">
<div className="skills">
<h1>Skills</h1>
{Object.keys(Skills).map(skill => <ul key={skill}>
{Skills[skill].map(item => <li key={item.id}>{item.id}</li>)}
</ul>}
</div>
</div>
);
};
Your frontend is a nested array with only one item, so the index is always 0. The following is the fixed version.
import React, { useEffect, useState } from "react";
import "../styles/Home.css";
import Skills from "../data/Skills";
export const Home = () => {
const [skills, setSkills] = useState([]);
useEffect(() => {
setSkills(Skills);
}, []);
const frontend = skills[0].frontend
console.log(frontend);
return (
<div className="home">
<div className="skills">
<h1>Skills</h1>
{frontend.map((item, index) => {
console.log(index)
return <ul key={index}>
<li>{item.id}</li>
</ul>;
})}
</div>
</div>
);
};
It seems your frontend.map((item, index) pointed to nested array, the simplest way was like this if you cannot change your backend result:
frontend[0].map((item, index)
I'm using ant design pro.
The idea is In the platform we have 2 languages to choose from Fr(French) and En(English),
I want the user when he logs in and change the language to English for example when he logs out and log in again the language should be saved to English so he would be able to see the content in English, I managed to do it, when i login in the backend the preferedLanguage = en, the language toggle in the also changes to en, the only problem the web page content stays in French it's only change in English when i reload the page.
I think the issue is related to the login page, the login page is set to French as default , let's say my preferred language now is English, if i login from the login page. The page content loaded in French only changes when i reload.
-This is the umijs documentation : https://umijs.org/docs/max/i18n#setlocale-%E8%AE%BE%E7%BD%AE%E8%AF%AD%E8%A8%80
-LanguageDropdown (the toggle where you select the language (Fr or En)
import type { FC } from 'react';
import React, { useState, useEffect } from 'react';
import { Menu, Dropdown } from 'antd';
import { CaretDownOutlined, CaretUpOutlined, GlobalOutlined } from '#ant-design/icons';
import styles from './index.less';
import { setLocale, getLocale, getAllLocales } from 'umi';
interface Props {
setUpdateLang: any;
currentLang: string;
}
const LanguageDropdown: FC<Props> = ({ currentLang, setUpdateLang }) => {
const [langvisible, setLangVisible] = useState<boolean>(false);
const [localesList, setLocalesList] = useState<any[]>([]);
const [currentLocale, setCurrentLocale] = useState<string>('');
// useEffect(() => {
// if (currentLang) {
// setLocalesList(getAllLocales());
// setCurrentLocale(getLocale());
// setLocale(currentLang === 'fr' ? 'fr-FR' : 'en-US');
// }
// alert(currentLang);
// alert(getLocale());
// }, [currentLang]);
useEffect(() => {
setLocalesList(getAllLocales());
setCurrentLocale(getLocale());
if (currentLang) {
// alert(222);
const selectedLang = currentLang === 'fr' ? 'fr-FR' : 'en-US';
// setNewLang(selectedLang);
setLocale(selectedLang, false);
setCurrentLocale(getLocale());
}
}, [currentLang]);
const onLangVisibleChange = (visibleLang: boolean) => {
setLangVisible(visibleLang);
};
const langNameHandler = (lang: string) => {
if (lang === 'en-US') return 'EN';
else if (lang === 'fr-FR') return 'FR';
return 'FR';
};
const setNewLang = (lang: string) => {
setUpdateLang({ lang: langNameHandler(lang).toLocaleLowerCase(), updated: true });
setLocale(lang);
};
const Langmenu = (
<Menu>
{localesList?.map((lang: any) => (
<Menu.Item key={lang}>
<a onClick={() => setNewLang(lang)}>{langNameHandler(lang)}</a>
</Menu.Item>
))}
</Menu>
);
return (
<div className={styles.profileDropdownContainer}>
<Dropdown
overlay={Langmenu}
placement="bottomLeft"
trigger={['click']}
onVisibleChange={onLangVisibleChange}
className={styles.dropdown}
>
<div className={styles.langContainer}>
<span>
<GlobalOutlined /> {langNameHandler(currentLocale)}
</span>
{!langvisible ? <CaretDownOutlined /> : <CaretUpOutlined />}
</div>
</Dropdown>
</div>
);
};
export default LanguageDropdown;
-RightContext
import { Tag } from 'antd';
import type { Settings as ProSettings } from '#ant-design/pro-layout';
import React, { useEffect, useState } from 'react';
import type { ConnectProps } from 'umi';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { ConnectState } from '#/models/connect';
import Avatar from './AvatarDropdown';
import styles from './index.less';
import LanguageDropdown from '../languageDropdown';
import moment from 'moment';
export type GlobalHeaderRightProps = {
dispatch: Dispatch;
theme?: ProSettings['navTheme'] | 'realDark';
auth: any;
users: any;
platformLanguage: any;
data: any;
} & Partial<ConnectProps> &
Partial<ProSettings>;
const ENVTagColor = {
dev: 'orange',
test: 'green',
pre: '#87d068',
};
const GlobalHeaderRight: React.FC<GlobalHeaderRightProps> = (props) => {
const [updateLang, setUpdateLang] = useState<{ lang: string; updated: boolean }>({
lang: '',
updated: false,
});
const [currentLang, setCurrentLang] = useState<any>(null);
const { theme, layout, auth, platformLanguage, data, dispatch } = props;
let className = styles.right;
useEffect(() => setCurrentLang(platformLanguage), [platformLanguage]);
useEffect(() => {
if (updateLang.updated) {
const {
organization,
roles,
email,
deleted,
department,
createdById,
organizationId,
...rest
} = data;
const birthdate = moment(rest.birthdate).format('YYYY-MM-DD');
const workversary = moment(rest.workversary).format('YYYY-MM-DD');
dispatch({
type: 'users/updateMe',
payload: {
data: { ...rest, birthdate, workversary, platformLanguage: updateLang.lang },
userId: auth?.currentUser.id,
},
});
setUpdateLang({ ...updateLang, updated: false });
setCurrentLang(updateLang.lang);
}
}, [updateLang]);
if (theme === 'dark' && layout === 'top') {
className = `${styles.right} ${styles.dark}`;
}
return (
<div className={className}>
{currentLang ? (
<LanguageDropdown currentLang={currentLang} setUpdateLang={setUpdateLang} />
) : null}
<Avatar />
{REACT_APP_ENV && (
<span>
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
</span>
)}
</div>
);
};
export default connect(({ settings, auth, users }: ConnectState) => ({
theme: settings.navTheme,
layout: settings.layout,
auth,
users,
platformLanguage: auth?.currentUser?.membership?.platformLanguage,
data: auth?.currentUser?.membership,
}))(GlobalHeaderRight);
-LoginLayout
import React, { useEffect, useState } from 'react';
import type { ConnectState } from '#/models/connect';
import type { MenuDataItem } from '#ant-design/pro-layout';
import { getMenuData, getPageTitle } from '#ant-design/pro-layout';
import { useIntl, connect } from 'umi';
import type { ConnectProps } from 'umi';
import { Col, Row } from 'antd';
import { Helmet, HelmetProvider } from 'react-helmet-async';
import LoginImage from '../assets/loginImage.png';
import SignUpAdminImage from '../assets/adminSignup.svg';
import styles from './LoginLayout.less';
import LanguageDropdown from '#/components/languageDropdown';
import SignupSideText from '#/components/SignupSideText';
export type UserLayoutProps = {
breadcrumbNameMap: Record<string, MenuDataItem>;
} & Partial<ConnectProps>;
const LoginLayout: React.FC<UserLayoutProps> = (props) => {
const [layoutImage, setLayoutImage] = useState(LoginImage);
const [updateLang, setUpdateLang] = useState<{ lang: string; updated: boolean }>({
lang: '',
updated: false,
});
const [currentLang, setCurrentLang] = useState<any>('');
useEffect(() => {
if (updateLang.updated) {
setUpdateLang({ ...updateLang, updated: false });
setCurrentLang(updateLang.lang);
}
}, [updateLang]);
useEffect(() => {
if (window.location.pathname === '/user/adminSignup/step1') {
setLayoutImage(SignUpAdminImage);
} else if (window.location.pathname === '/user/adminSignup/step2') {
setLayoutImage('TextSignup');
} else setLayoutImage(LoginImage);
}, [window.location.pathname]);
const {
route = {
routes: [],
},
} = props;
const { routes = [] } = route;
const {
children,
location = {
pathname: '',
},
} = props;
const { formatMessage } = useIntl();
const { breadcrumb } = getMenuData(routes);
const title = getPageTitle({
pathname: location.pathname,
formatMessage,
breadcrumb,
...props,
});
return (
<>
<HelmetProvider>
<Helmet>
<title>{title}</title>
<meta name="description" content={title} />
</Helmet>
<div className={styles.container}>
<Col
xl={{ span: 12, order: 1 }}
xs={{ span: 0, order: 2 }}
md={{ span: 0, order: 2 }}
lg={{ span: 0, order: 2 }}
style={{ backgroundColor: '#00bfa5' }}
>
{layoutImage === 'TextSignup' ? (
<SignupSideText />
) : (
<img alt="logo" width="100%" height="100%" src={layoutImage} />
)}
</Col>
<Col
xl={{ span: 12, order: 2 }}
lg={{ span: 24, order: 2 }}
sm={{ span: 24, order: 1 }}
xs={{ span: 24, order: 1 }}
>
{' '}
<Row justify="end" className="languageRow">
<LanguageDropdown currentLang={currentLang} setUpdateLang={setUpdateLang} />
</Row>
{children}
</Col>
</div>
</HelmetProvider>
</>
);
};
export default connect(({ settings }: ConnectState) => ({ ...settings }))(LoginLayout);
I'm having a hard time converting these 3 class components to function components, the class components are working i just am trying to convert them for learning purposes.
the API call: yelp.js
const { default: SearchBar } = require("../components/SearchBar/SearchBar");
const Yelp = {
searchYelp(term, location) {
return fetch(`/api/hello?term=${term}&location=${location}`)
.then((response) => {
// console.log(response)
return response.json()
}).then((jsonResponse) => {
// console.log(jsonResponse)
if (jsonResponse.businesses) {
return jsonResponse.businesses.map((business) => {
return {
id: business.id,
imageSrc: business.image_url,
name: business.name,
address: business.location.address1,
city: business.location.city,
state: business.location.state,
zipCode: business.location.zip_code,
category: business.categories.title,
rating: business.rating,
reviewCount: business.review_count,
}
})
}
})
}
}
export default Yelp
The Home component as a function that renders a SearchBar and BusinessList component: Home.js
import React, { useState } from "react";
import BusinessList from '../../../src/components/BusinessList/BusinessList';
import SearchBar from '../../../src/components/SearchBar/SearchBar';
import Yelp from '../../util/yelp';
const Home = (term, location) => {
const [businesses, setBusinesses] = useState([]);
const searchYelp = Yelp.searchYelp(term, location).then(businesses => {
setBusinesses(businesses)
})
return (
<>
<SearchBar searchYelp={searchYelp} />
<BusinessList business={businesses} />
</>
)
}
export default Home;
The Home component as a class: Home.js
// import React from 'react';
// import BusinessList from '../../../src/components/BusinessList/BusinessList';
// import SearchBar from '../../../src/components/SearchBar/SearchBar';
// import Yelp from '../../util/yelp';
// class Home extends React.Component {
// constructor() {
// super();
// this.state = {
// businesses: [],
// };
// this.searchYelp = this.searchYelp.bind(this);
// }
// searchYelp(term, location, sortBy) {
// Yelp.searchYelp(term, location, sortBy).then((businesses) => {
// this.setState({ businesses: businesses })
// })
// }
// render() {
// return (
// <>
// <SearchBar searchYelp={this.searchYelp} />
// <BusinessList businesses={this.state.businesses} />
// </>
// )
// }
// }
// export default Home;
The BusinessList component as a function that renders a Business component: BusinessList.js
import React, { useState } from "react";
import './BusinessList.css';
import Business from '../Business/Business';
function BusinessList(businesses) {
console.log(businesses)
return (
<div className="BusinessList">
{
businesses.map(business => {
<Business key={business.id} business={business} />
})
}
</div>
)
};
export default BusinessList;
The BusinessList component as a class: BusinessList.js
// import React from 'react';
// import './BusinessList.css';
// import Business from '../Business/Business';
// class BusinessList extends React.Component {
// constructor(props) {
// super(props)
// console.log(props.businesses)
// }
// render() {
// return (
// <div className="BusinessList">
// {
// this.props.businesses.map((business) => {
// return <Business key={business.id} business={business} />
// })
// }
// </div>
// )
// }
// };
// export default BusinessList;
The Business component as a function: Business.js
import React from "react";
import './Business.css';
const Business = (business) => {
return (
<div className="Business">
<div className="image-container">
<img src={business.business.imageSrc} alt={business.imageSrc} />
</div>
<h2>{business.business.name}</h2>
<div className="Business-information">
<div className="Business-address">
<p>{business.business.address}</p>
<p>{business.business.city} {business.state} {business.zipCode}</p>
</div>
<div className="Business-reviews">
<h3>{business.business.category}</h3>
<h3 className="rating">{business.business.rating}</h3>
<p>{business.business.reviewCount} reviews</p>
</div>
</div>
</div>
)
};
export default Business;
The Business component as a class: Business.js
// import React from "react";
// import './Business.css';
// class Business extends React.Component {
// render() {
// const { business } = this.props
// return (
// <div className="Business">
// <div className="image-container">
// <img src={business.imageSrc} alt={business.imageSrc} />
// </div>
// <h2>{business.name}</h2>
// <div className="Business-information">
// <div className="Business-address">
// <p>{business.address}</p>
// <p>{business.city} {business.state} {business.zipCode}</p>
// </div>
// <div className="Business-reviews">
// <h3>{business.category}</h3>
// <h3 className="rating">{business.rating}</h3>
// <p>{business.reviewCount} reviews</p>
// </div>
// </div>
// </div>
// )
// }
// };
// export default Business;
EDIT **
My attempt at SearchBar component as function: SearchBar.js
import React, { useState, useEffect } from "react";
import './SearchBar.css';
const SearchBar = (props) => {
const [term, setTerm] = useState('')
const [location, setLocation] = useState('')
const [sortBy, setSortBy] = useState('best_match')
const sortByOptions = {
'Best Match': 'best_match',
'Highest Rated': 'rating',
'Most Reviewed': 'review_count'
};
const handleSortByChange = () => {
setSortBy(sortBy)
// console.log(sortByOption)
console.log(sortBy)
}
const renderSortByOptions = (sortByOptions) => {
// console.log(Object.keys(sortByOptions))
return Object.keys(sortByOptions).map(sortByOption => {
let sortByOptionValue = sortByOptions[sortByOption]
// console.log(sortByOptionValue)
return <li
className={sortBy === sortByOption ? 'active' : ''}
onClick={handleSortByChange}
key={sortByOptionValue}>
{sortByOption}
</li>;
})
}
const handleTermChange = (event) => {
setTerm(event.target.value)
}
const handleLocationChange = (event) => {
setLocation(event.target.value)
}
const handleSearch = (event) => {
event.preventDefault()
props.searchYelp(term, location)
}
return (
<div className="SearchBar">
{props.searchYelp}
<div className="SearchBar-sort-options">
<ul>
{renderSortByOptions(sortByOptions)}
</ul>
</div>
<div className="SearchBar-fields">
<input
onChange={handleTermChange}
placeholder="Search Businesses"
/>
<input
onChange={handleLocationChange}
placeholder="Where?"
/>
<button className="SearchBar-submit" onClick={handleSearch}>Let's Go</button>
</div>
</div>
)
}
export default SearchBar;
EDIT**
SearchBar component as a class: SearchBar.js
import React from 'react';
import './SearchBar.css';
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
term: '',
location: '',
sortBy: 'best_match'
}
this.handleTermChange = this.handleTermChange.bind(this)
this.handleLocationChange = this.handleLocationChange.bind(this)
this.handleSearch = this.handleSearch.bind(this)
this.sortByOptions = {
'Best Match': 'best_match',
'Highest Rated': 'rating',
'Most Reviewed': 'review_count'
};
}
getSortByClass(sortByOption) {
// console.log(sortByOption)
if (this.state.sortBy === sortByOption) {
return 'active'
}
return ''
}
handleSortByChange(sortByOption) {
this.setState({
sortBy: sortByOption
})
}
handleTermChange(event) {
this.setState({
term: event.target.value
})
}
handleLocationChange(event) {
this.setState({
location: event.target.value
})
}
handleSearch(event) {
this.props.searchYelp(this.state.term, this.state.location, this.state.sortBy)
event.preventDefault()
}
renderSortByOptions() {
return Object.keys(this.sortByOptions).map(sortByOption => {
let sortByOptionValue = this.sortByOptions[sortByOption]
console.log(sortByOptionValue)
return <li
onClick={this.handleSortByChange.bind(this, sortByOptionValue)}
className={this.getSortByClass(sortByOptionValue)}
key={sortByOptionValue}>
{sortByOption}
</li>;
})
}
render() {
return (
<div className="SearchBar">
{this.searchYelp}
<div className="SearchBar-sort-options">
<ul>
{this.renderSortByOptions()}
</ul>
</div>
<div className="SearchBar-fields">
<input onChange={this.handleTermChange} placeholder="Search Businesses" />
<input onChange={this.handleLocationChange} placeholder="Where?" />
<button className="SearchBar-submit" onClick={this.handleSearch}>Let's Go</button>
</div>
</div>
)
}
};
export default SearchBar;
I keep getting the error "Cannot read properties of undefined (reading 'map')
Or the error "Businesses.map is not a function"
Im also a little confused as to why when everything is converted to function components in my final Business component in order to get things to showup im required to pass things in as business.business.imageSrc instead of just business.imageSrc
First in Home searchYelp should be declared as a function so it can be passed as a callback to the SearchBar component.
const Home = () => {
const [businesses, setBusinesses] = useState([]);
const searchYelp = (term, location) => {
Yelp.searchYelp(term, location)
.then(businesses => {
setBusinesses(businesses);
});
};
return (
<>
<SearchBar searchYelp={searchYelp} />
<BusinessList business={businesses} />
</>
)
};
Then in BusinessList you need to access the passed business prop. Your current code is naming the props object businesses and then attempts to map it. It could be businesses.business.map, but by convention we name the props object props or simply destructure the props you want to use. You need to also return the Business component you are mapping to.
function BusinessList({ business }) {
return (
<div className="BusinessList">
{business.map(business => {
return <Business key={business.id} business={business} />;
})}
</div>
)
};
Same issue with the props object name in the Business component.
const Business = (props) => {
return (
<div className="Business">
<div className="image-container">
<img src={props.business.imageSrc} alt={props.business.imageSrc} />
</div>
<h2>{props.business.name}</h2>
<div className="Business-information">
<div className="Business-address">
<p>{props.business.address}</p>
<p>{props.business.city} {props.business.state} {business.zipCode}</p>
</div>
<div className="Business-reviews">
<h3>{props.business.category}</h3>
<h3 className="rating">{props.business.rating}</h3>
<p>{props.business.reviewCount} reviews</p>
</div>
</div>
</div>
)
};
BusinessList receives props, an object containing the props passed in.
The function parameter would either need to destructure it:
function BusinessList({ businesses }) { ... }
Or reference it off the props object:
function BusinessList(props) {
console.log(props.businesses)
// ...
}
Few notes:
Right now Yelp.searchYelp returns Promise<any[] | undefined>, i.e undefined is a legitimate value that the consume may get. Up to you to decide if setBusinesses(businesses) when businesses is undefined is useful or not, but in that case, handle it. Otherwise default to an empty array, setBusinesses(businesses ?? []) or throw an error.
Do not run side effects in the render phase, i.e call the api inside a useEffect:
React.useEffect(() => {
const searchYelp = Yelp.searchYelp(term, location).then(businesses => {
setBusinesses(businesses ?? [])
})
}, [term, location])
Lastly, const Business = (business) => { here business is actually the props object. You can simply destructure it const Business = ({ business }) => { to get the value directly.
I am tickling with Algolia autocomplete, and I am trying to replicate their custom renderer in react using the class component. This is the sandbox of the minimal demo of custom renderer using functional component,
and here is my attempt to convert it into a class component.
import { createAutocomplete } from "#algolia/autocomplete-core";
import { getAlgoliaResults } from "#algolia/autocomplete-preset-algolia";
import algoliasearch from "algoliasearch/lite";
import React from "react";
const searchClient = algoliasearch(
"latency",
"6be0576ff61c053d5f9a3225e2a90f76"
);
// let autocomplete;
class AutocompleteClass extends React.PureComponent {
constructor(props) {
super(props);
this.inputRef = React.createRef();
this.autocomplete = null;
this.state = {
autocompleteState: {},
};
}
componentDidMount() {
if (!this.inputRef.current) {
return undefined;
}
this.autocomplete = createAutocomplete({
onStateChange({ state }) {
// (2) Synchronize the Autocomplete state with the React state.
this.setState({ autocompleteState: state });
},
getSources() {
return [
{
sourceId: "products",
getItems({ query }) {
return getAlgoliaResults({
searchClient,
queries: [
{
indexName: "instant_search",
query,
params: {
hitsPerPage: 5,
highlightPreTag: "<mark>",
highlightPostTag: "</mark>",
},
},
],
});
},
getItemUrl({ item }) {
return item.url;
},
},
];
},
});
}
render() {
const { autocompleteState } = this.state;
return (
<div className="aa-Autocomplete" {...this.autocomplete?.getRootProps({})}>
<form
className="aa-Form"
{...this.autocomplete?.getFormProps({
inputElement: this.inputRef.current,
})}
>
<div className="aa-InputWrapperPrefix">
<label
className="aa-Label"
{...this.autocomplete?.getLabelProps({})}
>
Search
</label>
</div>
<div className="aa-InputWrapper">
<input
className="aa-Input"
ref={this.inputRef}
{...this.autocomplete?.getInputProps({})}
/>componentDidUpdate()
</div>
</form>
<div className="aa-Panel" {...this.autocomplete?.getPanelProps({})}>
{autocompleteState.isOpen &&
autocompleteState.collections.map((collection, index) => {
const { source, items } = collection;
return (
<div key={`source-${index}`} className="aa-Source">
{items.length > 0 && (
<ul
className="aa-List"
{...this.autocomplete?.getListProps()}
>
{items.map((item) => (
<li
key={item.objectID}
className="aa-Item"
{...this.autocomplete?.getItemProps({
item,
source,
})}
>
{item.name}
</li>
))}
</ul>
)}
</div>
);
})}
</div>
</div>
);
}
}
export default AutocompleteClass;
and the sandbox of the same version, I also tried using componentDidUpdate() but no luck, any lead where I did wrong would be much appreciated thank you :)
Ok, dont know why you need it made into class component but here you go:
import { createAutocomplete } from "#algolia/autocomplete-core";
import { getAlgoliaResults } from "#algolia/autocomplete-preset-algolia";
import algoliasearch from "algoliasearch/lite";
import React from "react";
const searchClient = algoliasearch(
"latency",
"6be0576ff61c053d5f9a3225e2a90f76"
);
// let autocomplete;
class AutocompleteClass extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
autocompleteState: {},
query: '',
};
this.autocomplete = createAutocomplete({
onStateChange: this.onChange,
getSources() {
return [
{
sourceId: "products",
getItems({ query }) {
console.log('getting query', query)
return getAlgoliaResults({
searchClient,
queries: [
{
indexName: "instant_search",
query,
params: {
hitsPerPage: 5,
highlightPreTag: "<mark>",
highlightPostTag: "</mark>"
}
}
]
});
},
getItemUrl({ item }) {
return item.url;
}
}
];
}
});
}
onChange = ({ state }) => {
console.log(state)
this.setState({ autocompleteState: state, query: state.query });
}
render() {
const { autocompleteState } = this.state;
return (
<div className="aa-Autocomplete" {...this.autocomplete?.getRootProps({})}>
<form
className="aa-Form"
{...this.autocomplete?.getFormProps({
inputElement: this.state.query
})}
>
<div className="aa-InputWrapperPrefix">
<label
className="aa-Label"
{...this.autocomplete?.getLabelProps({})}
>
Search
</label>
</div>
<div className="aa-InputWrapper">
<input
className="aa-Input"
value={this.state.query}
{...this.autocomplete?.getInputProps({})}
/>
</div>
</form>
<div className="aa-Panel" {...this.autocomplete?.getPanelProps({})}>
{autocompleteState.isOpen &&
autocompleteState.collections.map((collection, index) => {
const { source, items } = collection;
return (
<div key={`source-${index}`} className="aa-Source">
{items.length > 0 && (
<ul
className="aa-List"
{...this.autocomplete?.getListProps()}
>
{items.map((item) => (
<li
key={item.objectID}
className="aa-Item"
{...this.autocomplete?.getItemProps({
item,
source
})}
>
{item.name}
</li>
))}
</ul>
)}
</div>
);
})}
</div>
</div>
);
}
}
export default AutocompleteClass;
Anyway the componentDidMount is called only once, and because of ref object is undefined it just returned from it.
Also messing with this in class components is quite a bad idea (that is why func components are recommended)
I am facing such problem, i got my array of records fetched from an API, mapped it into single elements and outputting them as single components. I have function which changes state of parent Component, passes value to child component and child component should hide/show div content after button is clicked.
Of course. It is working, but partially - my all divs are being hidden/shown. I have set specific key to each child component but it doesn't work.
App.js
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
import countries from '../../countriesList';
import CitySearchForm from './CitySearchForm/CitySearchForm';
import CityOutput from './CityOutput/CityOutput';
import ErrorMessage from './ErrorMessage/ErrorMessage';
class App extends Component {
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false
}
getCities = (e) => {
e.preventDefault();
const countryName = e.target.elements.country.value.charAt(0).toUpperCase() + e.target.elements.country.value.slice(1);
const countryUrl = 'https://api.openaq.org/v1/countries';
const wikiUrl ='https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&format=json&category=city&redirects&origin=*&titles=';
const allowedCountries = new RegExp(/spain|germany|poland|france/, 'i');
if (allowedCountries.test(countryName)) {
axios
.get(countryUrl)
.then( response => {
const country = response.data.results.find(el => el.name === countryName);
return axios.get(`https://api.openaq.org/v1/cities?country=${country.code}&order_by=count&sort=desc&limit=10`)
})
.then( response => {
const cities = response.data.results.map(record => {
return { name: record.city };
});
cities.forEach(city => {
axios
.get(wikiUrl + city.name)
.then( response => {
let id;
for (let key in response.data.query.pages) {
id = key;
}
const description = response.data.query.pages[id].extract;
this.setState(prevState => ({
cities: [...prevState.cities, {city: `${city.name}`, description}],
infoMessage: prevState.infoMessage = ''
}))
})
})
})
.catch(error => {
console.log('oopsie, something went wrong', error)
})
} else {
this.setState(prevState => ({
infoMessage: prevState.infoMessage = 'This is demo version of our application and is working only for Spain, Poland, Germany and France',
cities: [...prevState.cities = []]
}))
}
}
descriptionTogglerHandler = () => {
this.setState((prevState) => {
return { visible: !prevState.visible};
});
};
render () {
return (
<div className="App">
<ErrorMessage error={this.state.infoMessage}/>
<div className="form-wrapper">
<CitySearchForm getCities={this.getCities} getInformation={this.getInformation} countries={countries}/>
</div>
{this.state.cities.map(({ city, description }) => (
<CityOutput
key={city}
city={city}
description={description}
show={this.state.visible}
descriptionToggler={this.descriptionTogglerHandler} />
))}
</div>
);
}
}
export default App;
CityOutput.js
import React, { Component } from 'react';
import './CityOutput.css';
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show } = this.props;
let descriptionClasses = 'output-record description'
if (show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
export default CityOutput;
Put the visible key and the toggle function in the CityOutput instead of having it in the parent
import React, { Component } from "react";
import "./CityOutput.css";
class CityOutput extends Component {
state = {
visible: true
};
descriptionTogglerHandler = () => {
this.setState({ visible: !this.state.visible });
};
render() {
const { city, description } = this.props;
let descriptionClasses = "output-record description";
if (this.state.visible) {
descriptionClasses = "output-record description open";
}
return (
<div className="output">
<div className="output-record">
<b>City:</b> {city}
</div>
<button onClick={() => this.descriptionTogglerHandler()}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
);
}
}
export default CityOutput;
There are two ways of how I would approach this,
The first one is setting in your state a key property and check and compare that key with the child keys like:
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false.
currKey: 0
}
descriptionTogglerHandler = (key) => {
this.setState((prevState) => {
return { currKey: key, visible: !prevState.visible};
});
};
// then in your child component
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show, currKey, elKey } = this.props;
let descriptionClasses = 'output-record description'
if (show && elKey === currKey) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={() => descriptionToggler(elKey)}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
The other way is to set an isolated state for every child component
class CityOutput extends Component {
constructor(props) {
this.state = {
show: false
}
}
function descriptionToggler() {
const {show} = this.state;
this.setState({
show: !show
})
}
render() {
const { city, descriptionToggler, description } = this.props;
let descriptionClasses = 'output-record description'
if (this.state.show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
I hope this helps ;)