I am pretty much new to hooks so have a question here:
I have in React component function like
const getSection = assignmentSectionId => {
const findSection = sections.find(
section => section.sectionRefId === assignmentSectionId,
);
return findSection ? findSection.name : '';
};
and now I got suggestion to use useMemo on that function. Currently I am using like:
return (
<List
role="list"
aria-label={ariaLabel}
>
{assignments.map(assignment => {
const sectionName = getSection(assignment.sectionId);
return (
<Card
name={sectionName}
/>
);
})}
</List>
);
};
What is the best(optimal) way to use useMemo here if it is possible?
You could use the Array#map inside the useMemo .Its only re-render the list After assignments value change
const memoList = React.useMemo(()=> assignments.map(assignment => {
const sectionName = getSection(assignment.sectionId);
return (<Card name={sectionName}/>)
}),[assignments])
return (
<List role="list" aria-label={ariaLabel}>{memoList}</List>
);
};
Related
On a ReactJs project I try to parse with com.wiris.js.JsPluginViewer.parseElement(ref, true, function () {}); function.
According to the docs, that function is to be applied to specific elements on the DOM. In the samples with pure javascript, they first set innerHTML of dom element and then apply the function.
So I thought that it would be handled using refs in React. (not sure if its the best way)
I have an array which includes some string to be rendered as html. I took all refs as an array with useRef([]) and then set each element's ref using refs.current[index]
According to the type I directly render string with dangerouslySetInnerHTML or use a wrapper component to render with a child component on which I would use that special function.
But I couldn't reach innerHTML property of the ref before applying the function in the WirisWrapper. I tried ref.innerHTML and ref.current.innerHTML
Parser as Parent
import { useRef, createRef } from 'react';
import WirisWrapper from './WirisWrapper';
const Parser = ({ itemsArray }) => {
let refs = useRef([]);
refs.current = itemsArray.map((ref, index) => (refs.current[index] = createRef()));
return (
<div>
{itemsArray.map((item, index) =>
item.type === 'math' ? (
<WirisWrapper
ref={el => (refs.current[index] = el)}
key={index}
mString={item.html}
/>
) : (
<div key={index} dangerouslySetInnerHTML={{ __html: item.html}}>
</div>
)
)}
</div>
);
};
export default Parser;
WirisWrapper as Child
import { forwardRef, useEffect } from 'react';
const WirisWrapper = forwardRef((props, ref) => {
const { mString } = props;
useEffect(() => {
if (ref.current && com.wiris.js.JsPluginViewer) {
ref.current.innerHTML = mString;
com.wiris.js.JsPluginViewer.parseElement(ref, true, function () {});
}
}, [ref, com.wiris.js.JsPluginViewer]);
return <div ref={ref}></div>;
});
export default WirisWrapper;
refs.current = itemsArray.map((ref, index) => (refs.current[index] = createRef())); looks to be creating a new React ref each render cycle and mutating the existing array at the same time.
You want to only create React refs if they don't previously exist. Map the itemsArray array to a new array each render, returning existing refs or creating new refs.
refs.current = itemsArray.map((ref, i) => refs.current[index] ?? createRef()));
Then just access the ref by index when mapping the UI.
{itemsArray.map((item, index) =>
item.type === 'math' ? (
<WirisWrapper
ref={refs.current[index]}
key={index}
mString={item.html}
/>
) : (
<div key={index} dangerouslySetInnerHTML={{ __html: item.html}}>
</div>
)
)}
I started my learning path few months ago (html, css, js) and I have a question for an issue that I have with react (just started learning it).
I have an error that says : data.map is not a function
I want to loop trough my array of objects with map, and dispatch the props (title, answer) to the child for each loop, to make a list of different FaqComponent having each the {title and answer}
const data = useSelector(state => ({
...state.homePage.list
}))
console.log(data);
return (
<div>
{data.map((title, answer) => (
<FaqComponent
title={title}
answer={answer}
/>
))}
</div>
);
}
export default ...;
Thanks for your replies
You're using an object {} instead of an array [] syntax.
Try with:
const data = useSelector(state => ([
...state.homePage.list
]));
You should declare "data"s type, if it is an array so your function should be like :
const data = useSelector(state => ([
...state.homePage.list
]))
console.log(data);
return (
<div>
{(data && data.length > 0) ? data.map((item, i) => (
<FaqComponent
title={item.title}
answer={item.answer}
key={i}
/>))
: <div>No Result!...</div>}
</div>
);
}
export default ...;
I am converting class based components to react hooks. I got confused on using the ref parts. Because, the way I am using it complains me that The "innerRef" API has been removed in styled-components v4 in favor of React 16 ref forwarding, use "ref" instead like a typical component..
How do i make it work when using hooks?
const Tabs = ({activeTab, children}) => {
const [tabsElements, setTabsElements] = useState([])
return (
<TabsContext.TabProvider activeTab={activeTab}>
<TabsContext.TabConsumer>
{value => (
<ReactTabs>
<TabsContainer>
<ListTabs>
{value.context.tabs.map(tab => (
<TabTitleItem
key={tab.id}
onClick={value.context.onClick(tab)}
id={tab.id}
innerRef={tabElement => {
if (!tabsElements[tab.id]) {
setTabsElements(tabElements => ({
...tabElements,
[tab.id]: tabElement,
}))
}
}}
isActiveTab={value.context.activeTab.id === tab.id}
>
<TabAnchorItem>{tab.title}</TabAnchorItem>
</TabTitleItem>
))}
</ListTabs>
<ActiveTabBorder
activeTabElement={tabsElements[value.context.activeTab.id]}
/>
</TabsContainer>
{children}
</ReactTabs>
)}
</TabsContext.TabConsumer>
</TabsContext.TabProvider>
)
}
Here is the demo
https://codesandbox.io/s/z3moq8662p
First of all you cannot update state within the ref callback method. Secondly you simply need to pass ref instead of innerRef to the TabTitleItem component since it internally handles ref using forwardRef
const Tabs = ({ activeTab, children }) => {
const [tabsElements, setTabsElements] = useState([]);
const tabElements = useRef({});
return (
<TabsContext.TabProvider activeTab={activeTab}>
<TabsContext.TabConsumer>
{value => (
<ReactTabs>
<TabsContainer>
<ListTabs>
{console.log("value", value.context)}
{value.context.tabs.map(tab => (
<TabTitleItem
key={tab.id}
onClick={value.context.onClick(tab)}
id={tab.id}
ref={tabElement => {
tabElements.current[tab.id] = tabElement;
}}
isActiveTab={value.context.activeTab.id === tab.id}
>
<TabAnchorItem>{tab.title}</TabAnchorItem>
</TabTitleItem>
))}
</ListTabs>
<ActiveTabBorder
activeTabElement={tabsElements[value.context.activeTab.id]}
/>
</TabsContainer>
{children}
</ReactTabs>
)}
</TabsContext.TabConsumer>
</TabsContext.TabProvider>
);
};
Working demo
While learning to use MobX I wanted to update a string from an <input/>.
I know that in Smart Components I can just use onChange={this.variable.bind(this)}, but I don't understand how I can do so in the following scenario:
const dumbComponent = observer(({ prop }) => {
// prop is an object
// destruct1 is a string, destruct2 is an array
const { destruct1, destruct2 } = prop;
const list = destruct2.map((item, key) => (<li key={key} >{item}</li>));
return (
<div>
<h1>title</h1>
<h2>{destruct1}</h2>
// Relevent part start
<input classname="destruct" value={destruct1.bind(this)} />
// Relevent part end
<ul>{list}</ul>
</div>
);
});
export default TodoList;
Can I bind the value of input to destruct somehow?
Obviously, this code doesn't work. But I don't know what to do.
You could create an inline arrow function and alter the prop.destruct1 like this:
const dumbComponent = observer(({ prop }) => {
const { destruct1, destruct2 } = prop;
const list = destruct2.map((item, key) => <li key={key}>{item}</li>);
return (
<div>
<h1>title</h1>
<h2>{destruct1}</h2>
<input
classname="destruct"
value={destruct1}
onChange={e => prop.destruct1 = e.target.value}
/>
<ul>{list}</ul>
</div>
);
});
I have been trying to pass extra props own to the children being created with the .map function but I have not been able to succeed in passing them succesfully.
This is my code:
export const CommentsListShanghai = (props) => {
const newTimestamp = props.timestamp;
console.log(newTimestamp);
const comments = props.comments;
if (comments.length > 0 ) {
return (
<ButtonToolbar className="comment-list">
{comments.map((com) => {
return (
com.adminSpark ?
<CommentsModal
className="comments-modal"
data-comments-modal={props.newTimestamp}
key={ com._id }
comment={ com }
city={com.city}
person={com.person}
location={com.location}
title={com.title}
content={com.content}
fileLink={com.fileLink}
timestamp={com.timestamp}
createdBy={com.createdBy}
/> :
<CommentsModal
key={ com._id }
comment={ com }
city={com.city}
person={com.person}
location={com.location}
title={com.title}
content={com.content}
fileLink={com.fileLink}
timestamp={com.timestamp}
createdBy={com.createdBy} />
)
})}
</ButtonToolbar>
);
} else {
return (
<Alert bsStyle="warning">No sparks yet. Please add some!</Alert>
);
}
};
CommentsListShanghai.propTypes = {
comments: React.PropTypes.array,
};
I am able to pass all the props of the comments const that I created, the problem is that besides these props I also need to pass an extra prop which is available in the CommentsListShanghai. How am I able to pass an extra props to this array?
I am able to console.log(newTimestamp) without a problem but don't understand how I can pass it down to the .map function.
Instead of
data-comments-modal={props.newTimestamp}
just use
data-comments-modal={props.timestamp}
The props here is still referring to the context of CommentsListShanghai.