I'm wiriting a kibana plugin and I have some problems with a flyout component. That's my starting code:
export const InputPipelineDebugger = ({queryParams, setQueryParams, setConnectionType, setMessage}) => {
const onChangeTest = (e) => {
setMessage(e.target.value);
}
const onTabConnectionTypeClicked = (tab) => {
setConnectionType(tab.id);
}
var tabsConnection = [
{
id: 'http',
name: 'HTTP',
content: <HttpInput onChangeTest = {onChangeTest} queryParams = {queryParams} setQueryParams={setQueryParams} />
},
{
id: 'syslog',
name: 'SYSLOG',
content: <SyslogInput onChangeTest = {onChangeTest} />
},
{
id: 'beats',
name: 'BEAT',
content: <BeatsInput onChangeTest = {onChangeTest} />
}
];
return (
<EuiFlexItem>
<h3>Input</h3>
<EuiTabbedContent
tabs={tabsConnection}
initialSelectedTab={tabsConnection[0]}
autoFocus="selected"
onTabClick={tab => {
onTabConnectionTypeClicked(tab);
}} />
</EuiFlexItem>
);
}
And what I want is to dynamically build the tabs array according to the response from a rest call. So I was trying to use the useEffect method and for that I change the tabsConnection with a state (and a default value, that works WITHOUT the useEffect method) but is not working at all. Console saids to me that the 'content' value from the tabs array is undefined, like if it's not recognizing the imports.
How can I achieve my goal? Thanks for the support
export const InputPipelineDebugger = ({queryParams, setQueryParams, setConnectionType, setMessage}) => {
//initialized with a default value
const [tabs, setTabs] = useState([{
id: 'syslog',
name: 'SYSLOG',
content: <SyslogInput onChangeTest = {onChangeTest} />
}]);
const onChangeTest = (e) => {
setMessage(e.target.value);
}
const onTabConnectionTypeClicked = (tab) => {
setConnectionType(tab.id);
}
useEffect(()=>{
//rest call here;
//some logics
var x = [{
id: 'beats',
name: 'BEATS',
content: <BeatsInput onChangeTest = {onChangeTest} />
}];
setTabs(x);
}, []);
return (
<EuiFlexItem>
<h3>Input</h3>
<EuiTabbedContent
tabs={tabs}
initialSelectedTab={tabs[0]}
autoFocus="selected"
onTabClick={tab => {
onTabConnectionTypeClicked(tab);
}} />
</EuiFlexItem>
);
}
Errors from the console:
Uncaught TypeError: Cannot read property 'content' of undefined
at EuiTabbedContent.render
EDIT 1
Here the code of BeatsInput and SyslogInput:
import {
EuiText,
EuiTextArea,
EuiSpacer,
EuiFlexItem,
} from '#elastic/eui';
import React, { Fragment, useState } from 'react';
export const SyslogInput = ({onChangeTest}) => {
return (
<EuiFlexItem>
<EuiFlexItem >
<EuiSpacer />
<EuiText >
<EuiTextArea fullWidth={true}
style={{ height: "450px" }}
onChange={e => onChangeTest(e)}
placeholder="Scrivi l'input"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexItem>
)
}
import {
EuiText,
EuiTextArea,
EuiSpacer,
EuiFlexItem,
} from '#elastic/eui';
import React, { Fragment, useState } from 'react';
export const BeatsInput = ({onChangeTest}) => {
return (
<EuiFlexItem>
<EuiFlexItem >
<EuiSpacer />
<EuiText >
<EuiTextArea fullWidth={true}
style={{ height: "450px" }}
onChange={e => onChangeTest(e)}
placeholder="Scrivi l'input"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexItem>
)
}
Change initialSelectedTab to selectedTab [or just add it in addition to]
https://elastic.github.io/eui/#/navigation/tabs
You can also use the selectedTab and
onTabClick props to take complete control over tab selection. This can
be useful if you want to change tabs based on user interaction with
another part of the UI.
Or work around:
give tabs an empty default value
const [tabs, setTabs] = useState();
render the component conditionally around tabs
{tabs && (
<EuiTabbedContent
tabs={tabs}
initialSelectedTab={tabs[0]}
autoFocus="selected"
onTabClick={tab => {
onTabConnectionTypeClicked(tab);
}}
/>
)}
Related
The technology stack I use is React+TS+React-Router-V6
Now the page will pass parameters after clicking the button, and the page can change in real time and receive the parameters passed (as shown in the figure below).
enter image description here
But I hope I can achieve the same effect as the following picture, the page will be re-rendered after clicking. I used useEffect to capture id parameters, but there was no effect, what should I do to achieve the same effect as the following picture
The parameter of my route parameter is id. The route is defined as follows.
const routes: RouteObject[] = [
{ path: "/", element: <Navigate to="/discover" /> },
{
path: "/discover",
element: <Discover />,
children: [
{ path: "/discover", element: <Navigate to="/discover/recommend" /> },
{ path: "/discover/recommend", element: <Recommend /> },
{ path: "/discover/ranking", element: <Ranking /> },
{ path: "/discover/singers", element: <Singers /> },
{ path: "/discover/songs", element: <Songs /> },
{ path: "/discover/album", element: <Album /> },
{
path: "/discover/djradio",
element: <DJRadio />,
children: [
{ path: "/discover/djradio/category", element: <Category /> },
],
},
],
},
{ path: "/mine", element: <Mine /> },
{ path: "/focus", element: <Focus /> },
{ path: "/download", element: <Download /> },
];
export default routes;
enter image description here
Here is the code for my component
import { Carousel } from "antd";
import React, { ReactNode, useEffect, useRef, memo, ElementRef } from "react";
import { Outlet, useNavigate } from "react-router-dom";
import { getRadioDataAction } from "./store";
import { useBearDispatch, useBearSelector } from "#/store";
import { RadioWrapper } from "./style";
import { RadioItemWrapper } from "./style";
import classNames from "classnames";
import { useQuery } from "#/utils/useQuery";
interface IProps {
children?: ReactNode;
}
const DJRadio: React.FC<IProps> = () => {
const dispatch = useBearDispatch();
const ref = useRef<ElementRef<typeof Carousel>>(null);
const navigate = useNavigate();
const { id } = useQuery();
const currentID = id;
const { catelist } = useBearSelector((state) => ({
catelist: state.radio.catelist,
}));
useEffect(() => {
dispatch(getRadioDataAction());
}, [id]);
function handleChange(isNext: boolean) {
if (isNext) {
ref.current?.next();
} else {
ref.current?.prev();
}
}
function handleClickTo(id: number) {
const wantUrl = "/discover/djradio/category?id=" + id;
navigate(wantUrl);
}
return (
<RadioWrapper className="all-bg">
<div className="wrap-v2 content">
<div className="content-top">
<div
className="my-left sprite-dj-btn"
onClick={() => {
handleChange(false);
}}
></div>
<Carousel className="carousel" ref={ref}>
{[0, 1].map((item: any, index: number) => {
return (
<div className="dj-content" key={index}>
{catelist &&
catelist
.slice(index * 18, (index + 1) * 18)
.map((element: any) => {
return (
<RadioItemWrapper
key={element.id}
imgUrl={element.picWebUrl}
onClick={() => handleClickTo(element.id)}
className={classNames({
active: currentID == element.id,
})}
>
<div className="img-content">
<div className="item-img"></div>
</div>
<span className="item-name">{element.name}</span>
</RadioItemWrapper>
);
})}
</div>
);
})}
</Carousel>
<div
className="my-right sprite-dj-btn"
onClick={() => {
handleChange(true);
}}
></div>
</div>
<div>{id}</div>
<Outlet></Outlet>
</div>
</RadioWrapper>
);
};
export default memo(DJRadio);
a page reloads after a route parameter is passed
I just want a reload effect. useEffect does not rerender when used.
The useEffect is run as the result of the component having been rendered; it doesn't itself cause any rerenders or page reloads.
A trivial solution to "reload" the "/discover/djradio/category" page would be to call window.location.reload from a useEffect hook in Category when the id queryString parameter value changes.
Example:
import { useSearchParams } from 'react-router-dom';
const Category = () => {
const [searchParams] = useSearchParams();
const id = searchParams.get("id");
React.useEffect(() => {
window.location.reload();
}, [id]);
...
};
This would actually make a network request to the server for the page/react app and might not be ideal. You could fake a reload with some state that just conditionally renders null for a second or to and then switches back to the Category UI JSX.
Example:
import { useSearchParams } from 'react-router-dom';
const Category = () => {
const [searchParams] = useSearchParams();
const [loading, setLoading] = React.useState(true);
const id = searchParams.get("id");
React.useEffect(() => {
setLoading(true);
const timer = setTimeout(() => {
setLoading(false);
}, 3000);
return () => {
clearTimeout(timer);
};
}, [id]);
...
if (loading) {
return null; // or loading indicator/spinner/etc...
}
...
};
I'm not getting any kind of entity data for Mention or Hashtag while using #draft-js-plugins. The link entity only working if I paste the URL directly on the editor. Mention is working fine but the hashtag or URL entity not working.
Hashtag and URL are detected properly on the editor but the editorState is not returning the entity data for hashtag and URL on typing.
import React, { ReactElement, useCallback, useMemo, useRef, useState } from 'react';
import { convertToRaw, EditorState } from 'draft-js';
import Editor, { createEditorStateWithText } from '#draft-js-plugins/editor';
import createHashtagPlugin from '#draft-js-plugins/hashtag';
import createLinkifyPlugin, { extractLinks } from '#draft-js-plugins/linkify';
import tlds from 'tlds';
import linkifyIt from 'linkify-it';
import createMentionPlugin, { defaultSuggestionsFilter } from '#draft-js-plugins/mention';
import editorStyles from './lib/styles/EditorStyles.module.css';
import hashtagStyles from './lib/styles/HashtagStyles.module.css';
import mentionStyles from './lib/styles/MentionStyles.module.css';
import linkStyles from './lib/styles/LinkStyles.module.css';
import '#draft-js-plugins/mention/lib/plugin.css';
import mentions from './lib/data/mentions';
import { EntryComponentProps } from '#draft-js-plugins/mention/lib/MentionSuggestions/Entry/Entry';
// mention suggestion component
function Entry(props: EntryComponentProps): ReactElement {
const { mention, theme, searchValue, isFocused, ...parentProps } = props;
return (
<div {...parentProps}>
<div className={theme?.mentionSuggestionsEntryContainer}>
<div className={theme?.mentionSuggestionsEntryContainerLeft}>
<img
src={mention.avatar}
className={theme?.mentionSuggestionsEntryAvatar}
role='presentation'
/>
</div>
<div className={theme?.mentionSuggestionsEntryContainerRight}>
<div className={theme?.mentionSuggestionsEntryText}>{mention.name}</div>
<div className={theme?.mentionSuggestionsEntryTitle}>{mention.title}</div>
</div>
</div>
</div>
);
}
export default function DynamicPostInputArea({
editorKey = 'comment',
placeholder = 'Write a comment',
}) {
const ref = useRef<Editor>(null);
// editor state
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const [open, setOpen] = useState(false);
const [suggestions, setSuggestions] = useState(mentions);
// =================================================================
// == set plugins & configuration settings for the dynamic post input area
// =================================================================
const { plugins, MentionSuggestions } = useMemo(() => {
// link detection plugin
const linkifyPlugin = createLinkifyPlugin({
customExtractLinks: (text) => linkifyIt().tlds(tlds).set({ fuzzyEmail: false }).match(text),
component(props) {
// eslint-disable-next-line no-alert, jsx-a11y/anchor-has-content
return <a style={{ color: 'var(--link)' }} {...props} />;
},
});
// hashtag detection plugin
const hashtagPlugin = createHashtagPlugin({
theme: hashtagStyles,
});
// mention detection plugin
const mentionPlugin = createMentionPlugin({
// entityMutability: 'IMMUTABLE',
supportWhitespace: true,
});
const { MentionSuggestions } = mentionPlugin;
const plugins = [hashtagPlugin, linkifyPlugin, mentionPlugin];
return { plugins, MentionSuggestions };
}, []);
// =================================================================
// == mention modifier
// =================================================================
const onOpenChange = useCallback((_open: boolean) => {
setOpen(_open);
}, []);
const onSearchChange = useCallback(({ value }: { value: string }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions));
}, []);
// =================================================================
// == on change of editor inputs this function will be called
// =================================================================
const onChange = useCallback((_editorState: EditorState) => {
setEditorState(_editorState);
const contentState = _editorState.getCurrentContent();
const inputData = {
text: _editorState.getCurrentContent().getPlainText('\u0001'),
content: convertToRaw(contentState),
entityMap: contentState.getAllEntities(),
};
console.log(inputData);
}, []);
return (
<>
<div
className={editorStyles.editor}
onClick={() => {
ref.current!.focus();
}}>
<Editor
ref={ref}
placeholder={placeholder}
editorState={editorState}
editorKey={editorKey}
onChange={onChange}
// onChange={setEditorState}
plugins={plugins}
// decorators={}
// keyBindingFn={bindedKeys}
/>
<MentionSuggestions
open={open}
onOpenChange={onOpenChange}
suggestions={suggestions}
onSearchChange={onSearchChange}
// onAddMention={() => {
// // get the mention object selected
// }}
// entryComponent={Entry}
popoverContainer={({ children }) => <div>{children}</div>}
/>
</div>
<style jsx global>{`
.public-DraftEditorPlaceholder-root {
position: absolute;
}
.public-DraftEditorPlaceholder-root > * {
color: var(--outline);
font-size: 0.875rem;
}
`}</style>
</>
);
}
I need some data like this for every hashtags & URLs
...
entityRanges: [
{
offset: 7,
length: 14,
key: 2,
},
{
offset: 25,
length: 7,
key: 3,
}]
...
entityMap: {
'2': {
type: 'LINK',
mutability: 'MUTABLE',
data: {
href: 'http://helloworld.com/',
rel: 'noreferrer noopener',
target: '_self',
url: 'http://helloworld.com/',
},
},
'3': {
type: 'LINK',
mutability: 'MUTABLE',
data: {
href: 'http://abcd.co/',
rel: 'noreferrer noopener',
target: '_self',
url: 'http://abcd.co/',
},
},
}
...
If anyone can help me solve this it'll be a great help for me. Thank you.
I'm trying to use the Tab Component from semantic-ui-react and facing this error. I know that you can only return jsx/react component from such a function but as you can see I need that list that would be required for the Tab component from semantic-ui-react.
I will need the context variables anyway to do this.
Someone advised to just return the render: method's jsx through a function and there use the hooks but then menuItem: still needs the context.
TypeError: TestsList is not iterable
./src/components/SideBar/TestsMenu.js/<
C:/Users/Dell/OneDrive/Desktop/niroggyan/nirogyan/src/components/SideBar/TestsMenu.js:72
69 |
70 |
71 |
> 72 | const TestsMenu = [TestsHeader, ...TestsList]
73 |
74 | export default TestsMenu;
75 |
TestsMenu.js
import { Header, Button, Message, Tab } from "semantic-ui-react";
import TestDetails from "../FormComponents/TestDetails";
import { TestContext, TabContext } from "../Main";
import { useContext } from "react";
import { useFormContext } from "react-hook-form";
import AddTestButton from "../FormComponents/AddTestButton";
import Search from "./SearchItem";
const TestsHeader = {
menuItem: {
header: true,
content: (
<>
<Header as="h3">Tests</Header>{" "}
<Search />
</>
),
active: false,
disabled: true,
},
render: () => <Tab.Pane key={"Tests Heading"}></Tab.Pane>,
};
const TestsList = () => {
const { testList, setTestList } = useContext(TestContext);
const { setCurrentTabIndex } = useContext(TabContext)
const { unregister } = useFormContext();
const handleTestDeletion = (e, testID) => {
const filterDict = (dict, filterFunc) =>
Object.fromEntries(Object.entries(dict).filter(filterFunc));
setTestList((state) => filterDict(state, ([key, val]) => key !== testID));
unregister(`patient.tests.${testID}`);
setCurrentTabIndex(0);
};
return Object.keys(testList).map((testID) => {
return {
menuItem: { content: testList[testID].text, color: "violet" },
render: () => (
<Tab.Pane key={testList[testID].text}>
<Message
attached
header="Test Details"
content="Fill out the test details."
/>
<TestDetails testID={testID} />
<AddTestButton />
<Button
color="red"
type="button"
onClick={(event) => handleTestDeletion(event, testID)}
>
Remove test
</Button>
<Button color="violet" type="submit">
Submit
</Button>
</Tab.Pane>
),
};
})
}
const TestsMenu = [TestsHeader, ...TestsList]
export default TestsMenu;
-----------------------------------------
Patients.js
// like TestsMenu.js
----------------------------------------
Main.js
const Main = () => {
// some code
const menuItems = [...ProfileMenu, ...TestsMenu];
return <>
<Tab
menu={{ fluid: true, vertical: true, tabular: true }}
panes={menuItems}
activeIndex={currentTabIndex}
onTabChange={(e, { activeIndex }) => {
setCurrentTabIndex(activeIndex);
}}
/>
</>
}
EDIT:
The answer removed the error from the gui but didn't do much help because the Array received, TestsMenu is of the form [Object, a function] and thus when I add a new Test, it's not visible in the Sidebar.
CodeSand Box
TestsList is a function. You cannot spread it inside a Array like [TestsHeader, ...TestsList]. It should be[TestsHeader, ...TestsList()].
As per the semantic-ui-react docs for Tab component, the panes prop should be an array of objects of the below type
{
"menuItem":"custom",
"pane":"custom",
"render":"function"
}
Your function TestList returns an Array of the above object. So you just need to call the function TestList and spread it like [TestsHeader, ...TestsList()]
Had to do it the following way. No other thing was working. Hooks and handleTestDeletion had to be moved to Main.js
import { Header, Button, Message, Tab } from "semantic-ui-react";
import TestDetails from "../FormComponents/TestDetails";
import AddTestButton from "../FormComponents/AddTestButton";
import Search from "./SearchItem";
const getTestHeader = () => {
return {
menuItem: {
header: true,
content: (
<>
<Header as="h3">Tests</Header>{" "}
<Search />
</>
),
active: false,
disabled: true,
},
render: () => <Tab.Pane key={"Tests Heading"}></Tab.Pane>,
}
};
const getTestList = (testList, handleTestDeletion) => {
return Object.keys(testList).map((testID) => {
return {
menuItem: { content: testList[testID].text, color: "violet" },
render: () => (
<Tab.Pane key={testList[testID].text}>
<Message
attached
header="Test Details"
content="Fill out the test details."
/>
<TestDetails testID={testID} />
<AddTestButton />
<Button
color="red"
type="button"
onClick={(event) => handleTestDeletion(event, testID)}
>
Remove test
</Button>
<Button color="violet" type="submit">
Submit
</Button>
</Tab.Pane>
),
};
})
}
const getTestMenu = (testList, handleTestDeletion) => {
return [getTestHeader(), ...getTestList(testList, handleTestDeletion)]
}
export default getTestMenu;
I have a list of objects that I'd like to pass to a map function which passes each object as props to a component to be rendered.
I have a menu and clicking each item calls setActiveItem() updating activeItem which is being managed by useState hook.
I'm trying to filter the list of objects based on this activeItem value. I've created a base case trying to replicate the problem but my base case works flawlessly though it'll at least clarify what I'm trying to do so here it is:
import React, { useState } from 'react';
import { Menu } from 'semantic-ui-react';
const [ALL, NUMBER, LETTER] = ['All', 'Number', 'Letter'];
const data = [
{
tags: [ALL, NUMBER],
value: '1'
},
{
tags: [ALL, LETTER],
value: 'a'
},
{
tags: [ALL, NUMBER],
value: '2'
},
{
tags: [ALL, LETTER],
value: 'b'
},
{
tags: [ALL, NUMBER],
value: '3'
},
{
tags: [ALL, LETTER],
value: 'c'
},
{
tags: [ALL, NUMBER],
value: '4'
},
{
tags: [ALL, LETTER],
value: 'd'
}
];
const renderData = (allValues, filterTag) => {
let filteredList = allValues.filter(val => {
return val['tags'].includes(filterTag);
});
return (
<div>
{filteredList.map(object_ => {
return object_.value;
})}
</div>
);
};
const BaseCase = props => {
const [activeItem, setActiveItem] = useState(ALL);
return (
<div>
<Menu inverted stackable fluid widths={4}>
<Menu.Item
name={ALL}
active={activeItem === ALL}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={NUMBER}
active={activeItem === NUMBER}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={LETTER}
active={activeItem === LETTER}
onClick={(e, { name }) => setActiveItem(name)}
/>
</Menu>
<div>{renderData(data, activeItem)}</div>
</div>
);
};
export default BaseCase;
Clicking number only shows numbers and everything else works as expected. Now for my component that isn't working. I have my data in a separate file like so:
import { BASH, DATA_SCIENCE, WEB_DEV, ALL } from '../constants';
const data = [
{
tags: [ALL],
title: 'Concussion App for Athletes',
.
.
.
},
{
tags: [DATA_SCIENCE, ALL],
title: 'Deep Learning: Exploring Car Value with an ANN',
...
},
.
.
.
];
export default data;
Here's my component. There's some commented out code that I tried but that also gave incorrect components being displayed.
import React, { useState } from 'react';
import ProjectCardContainer from '../../containers/ProjectCardContainer';
import { Menu } from 'semantic-ui-react';
import { ALL, BASH, DATA_SCIENCE, WEB_DEV } from './constants';
import data from './project_data';
import './Projects.scss';
const styles = {
container: {
display: 'flex',
justifyContent: 'space-around'
},
columns: {
display: 'flex',
flexDirection: 'column',
marginTop: '11px'
}
};
const renderColumn = (projectList, filterTag) => {
let projects = projectList.filter(proj => {
return proj['tags'].includes(filterTag);
});
return (
<div style={styles.columns}>
{projects.map(project => {
return <ProjectCardContainer project={project} />;
})}
</div>
);
};
const Projects = () => {
const [activeItem, setActiveItem] = useState(ALL);
// const [, updateState] = React.useState();
// const forceUpdate = useCallback(() => updateState({}), []);
// useEffect(() => {
// setTimeout(forceUpdate, 100);
// }, [activeItem]);
return (
<div>
<div className='second-nav-container'>
<Menu inverted stackable fluid widths={4}>
<Menu.Item
name={ALL}
active={activeItem === ALL}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={WEB_DEV}
active={activeItem === WEB_DEV}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={DATA_SCIENCE}
active={activeItem === DATA_SCIENCE}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={BASH}
active={activeItem === BASH}
onClick={(e, { name }) => setActiveItem(name)}
/>
</Menu>
</div>
<div style={styles.container}>{renderColumn(data, activeItem)}</div>
</div>
);
};
export default Projects;
Basically the rendered list of components usually isn't correct except maybe when the page is refreshed and the default value from useState() is used. Selecting from the menu doesn't show the components of the correct category.
I believe the problem is that the render function is getting called before activeItem is updated but I'm not sure how to work around that issue. I'm somewhat new to using hooks but this seems like a problem that must come up a lot.
Anyone Have any ideas how I can use a menu like this to filter data then only show specific components based on filtered data?
The problem in the end was I wasn't providing a unique key while rendering lists of components. The solution is to provide a unique key like so:
const renderColumn = (projectList, filterTag) => {
let projects = projectList.filter(proj => {
return proj['tags'].includes(filterTag);
});
return (
<div style={styles.columns}>
{projects.map(project => {
return <ProjectCardContainer key={project.title} project={project} />;
})}
</div>
);
};
In my case I know the titles will be unique so this works.
I don't think we need to mess around too much with complicated state management. I updated the base case to meet your needs:
Constants.js:
export const [ALL, DATA_SCIENCE, WEB_DEV, BASH] = ['All', 'DATA_SCIENCE', 'WEB_DEV', 'BASH'];
data.js:
import {ALL, DATA_SCIENCE, WEB_DEV, BASH} from './Constants';
const data = [
{
tags: [ALL],
title: 'Concussion App for Athletes',
},
{
tags: [DATA_SCIENCE, ALL],
title: 'Deep Learning: Exploring Car Value with an ANN',
},
{
tags: [BASH, ALL],
title: 'Bash 101'
},
{
tags: [WEB_DEV, ALL],
title: 'Web Development Book'
},
{
tags: [WEB_DEV, ALL],
title: 'Fundamentals of web design'
}
]
export default {data};
BaseCase.js:
import React, { useState } from 'react';
import { Menu } from 'semantic-ui-react';
import data from './data';
import {ALL, DATA_SCIENCE, WEB_DEV, BASH} from './Constants';
const renderData = (allValues, filterTag) => {
let filteredList = Object.values(allValues.data).filter(val => {
return val['tags'].includes(filterTag);
});
return (
<div>
{filteredList.map(object_ => {
return <p>{object_.title}</p>;
})}
</div>
);
};
const BaseCase = props => {
const [activeItem, setActiveItem] = useState(ALL);
const newData = data;
return (
<div>
<Menu inverted stackable fluid widths={4}>
<Menu.Item
name={ALL}
active={activeItem === ALL}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={DATA_SCIENCE}
active={activeItem === DATA_SCIENCE}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={WEB_DEV}
active={activeItem === WEB_DEV}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={BASH}
active={activeItem === BASH}
onClick={(e, { name }) => setActiveItem(name)}
/>
</Menu>
<div>{renderData(newData, activeItem)}</div>
</div>
);
};
export default BaseCase;
At return <p>{object_.title}</p>; render out your component like <ProjectCardContainer project={object_} />
I have this component that I split for easy management. Before splitting everything worked as expected, after splitting I am getting an error when I click on an icon which calls the createReactionsIcon. Error says
TypeError: updateReaction is not a function
onClick
./components/home/components/SingleUpload.jsx:26
23 |
24 | return icons.map(({ key, text, type }) => (
25 | <IconText
> 26 | onClick={() => updateReaction(item.id, key)}
| ^ 27 | key={key}
28 | type={type}
29 | text={text}
How can I access this correctly from my Home component where updateReaction is returning updateReaction from the redux store.
SubComponent
import PropTypes from 'prop-types';
import React from 'react';
import { Avatar, Card, Icon, List } from 'antd';
import { LIST_TEXTS, STYLES } from '../constants';
const { AVATAR, CARD_CONTAINER, CARD_LIST, ICON, USER_LIST } = STYLES;
const { INNER, MORE, UPLOAD, VERTICAL } = LIST_TEXTS;
const IconText = ({ type, text, onClick }) => (
<span>
<Icon type={type} style={ICON} onClick={onClick} />
{text}
</span>
);
function createReactionsIcon(item, updateReaction) {
const { like, dislike, maybe } = item.reactions;
const icons = [
{ key: 'like', text: `${like.count}`, type: 'heart' },
{ key: 'dislike', text: `${dislike.count}`, type: 'dislike' },
{ key: 'maybe', text: `${maybe.count}`, type: 'meh' },
];
return icons.map(({ key, text, type }) => (
<IconText
onClick={() => updateReaction(item.id, key)}
key={key}
type={type}
text={text}
/>
));
}
export default class SingleUpload extends React.Component {
render() {
const { values } = this.props;
return (
<div style={CARD_CONTAINER}>
<List
itemLayout={VERTICAL}
dataSource={values}
renderItem={item => {
const { avatar, description, id, uploader: { image, name } } = item;
return (
<List.Item style={USER_LIST}>
<Card
actions={createReactionsIcon(item, this.updateReaction)}
cover={<img alt={UPLOAD} src={image} />}
extra={<Icon type={MORE} />}
hoverable
key={id}
title={(
<a href="/">
<Avatar src={avatar} style={AVATAR} />
{name}
</a>
)}
type={INNER}
style={CARD_LIST}
>
{description}
</Card>
</List.Item>
);
}}
/>
</div>
);
}
}
Home.js
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import SingleUpload from './SingleUpload';
import ComparisonUpload from './ComparisonUpload';
import { STYLES } from '../constants';
import * as actions from '../actions';
import { getUploads } from '../selectors';
const { CARD_CONTAINER } = STYLES;
class Home extends React.Component {
componentDidMount() {
const { actions: { requestUploadList } } = this.props;
requestUploadList();
}
updateReaction = (id, reaction) => {
const { actions: { updateReaction } } = this.props;
const payload = { id, reaction };
updateReaction(payload);
}
render() {
const { uploads } = this.props;
return (
<div style={CARD_CONTAINER}>
<SingleUpload values={[...uploads.values()]} />
<ComparisonUpload values={[...uploads.values()]} />
</div>
);
}
}
Home.propTypes = {
actions: PropTypes.objectOf(PropTypes.object),
uploads: PropTypes.instanceOf(Map),
};
const mapStateToProps = state => ({
uploads: getUploads(state),
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Pass your function to component as props,
<SingleUpload values={[...uploads.values()]} updateReaction = {this.updateReaction}/>
Now you can use this in your child component,
<IconText onClick={() => this.props.updateReaction(item.id, key)}
You can pass the updateReaction from your parent to child as a callback
<SingleUpload values={[...uploads.values()]} hanldeReaction={this.updateReaction} />
And you can access it in the child using props.hanldeReaction
<Card actions={createReactionsIcon(item, this.props.hanldeReaction)}
You have to pass down the updateReaction() event-handler you defined in Home as a prop to SingleUpload. Then you can access that prop from anywhere inside your component.
Which means we can cleanup the actions prop inside the Card since we only need to pass the item now.
<Card actions={createReactionsIcon(item)}
As well as createReactionsIcon, now we just call that prop directly inside the function
function createReactionsIcon(item) {
const { like, dislike, maybe } = item.reactions;
const icons = [
{ key: 'like', text: `${like.count}`, type: 'heart' },
{ key: 'dislike', text: `${dislike.count}`, type: 'dislike' },
{ key: 'maybe', text: `${maybe.count}`, type: 'meh' },
];
return icons.map(({ key, text, type }) => (
<IconText
onClick={() => this.props.updateReaction(item.id, key)}
key={key}
type={type}
text={text}
/>
));
}
Less redundant code overall which sounds like what you are trying to achieve.