How to pass "parameter" to react component - javascript

I have a quick question, I am attempting to refactor my code in a existing codebase, this codebase uses a render function, however I would like to replace this with a react functional component instead.
However I am getting a error when I am passing in a parameter called "searchResults" which is a list.
Here is the previous non-refactored code
searchResults.sort((a, b) => new Date(b[0].created) - new Date(a[0].created));
const Rows = [];
searchResults.forEach((appResults, i) => {
Rows.push({
cells: [
appResults[0].applicationId,
<>{renderSuccessCount(appResults)}</>,
prettifyDate(appResults[0].created)
],
isOpen: false
});
This is my refactored code
const newRows = [];
searchResults.forEach((appResults, i) => {
newRows.push({
cells: [
appResults[0].applicationId,
<SuccessCount>{appResults}={appResults}</SuccessCount>,
prettifyDate(appResults[0].created)
],
isOpen: false
});
This is the function SuccessCount (in the non refactored code, renderSuccessCount was just renamed to SuccessCount in the refactored version
const SuccessCount = (appResults: AppResult[]) => {
const nSuccesses = appResults.filter((result: AppResult) => result.pulseStatus === SUCCESS).length;
return Something;
};
My question is relatively simple how do I pass appResults into my functional component, my current refactored code gives me this error
TS2740: Type '{ children: any[]; }' is missing the following properties from type 'AppResult[]': length, pop, push, concat, and 26 more.
This is in the linter, how do I get rid of it?

Now that SuccessCount is not a regular function but a react functional component, it'll not receive regular arguments. You'll have to access them via props
const SuccessCount = ({appResults}: {appResults: AppResult[]}) => {
Same as
const SuccessCount = (props) => {
const {appResults} = props; // or const appResults = props.appResults;
}
Usage as functional component
<SuccessCount appResults={appResults} />

Related

Destructuring react hook results in: This expression is not callable

I'm creating a custom hook and want to return an object and two functions when the hook is called. I do not want to return it as return {body, setProperty, setBody}, since I might call the hook multiple times within the same component and need different names for the variables.
I'd want to call it just as useState where I can destructure it as an array const [firstBody, setFirstBodyProp, setFirstBody] = useJSONState({/*Some code*/}), but when I try to return it as such return [body, setProperty, setBody], I get the following error when calling it from a component:
This expression is not callable.
Type 'jsonType' has no call signatures.ts(2349)
My Code:
type jsonType = {
[property: string]: any
}
const useJSONState = (json: jsonType) => {
const [ body, setBody ] = useState(json)
function setProp(property: string, value: any){
let newBody = {...body}
newBody[property] = value
setBody(newBody)
}
return [body, setProp, setBody]
}
export default useJSONState
The reason for the error is TypeScript inference. Your hook is returning an array of body,setProp,setBody but TypeScript infers the type to be jsonType[], which is the type of body(the first element in the array). To solve this error, you have to specify the return type of the hook explicitly.
export const useJSONState = (
json: jsonType
): [
jsonType,
(property: string, value: any) => void,
React.Dispatch<React.SetStateAction<jsonType>>
] => {
// .....
return [body, setProp, setBody];
}

Each child in a list should have a unique "key" prop. still even after assgining a key

Just I have been fiddling with ReactJs and ASP.net core and I am wondering why it throws an error that each component should have a key and when I have modified my code to assign a key it keeps displaying the same messagem how could I solve it?
react.development.js:401 Warning: Each child in a list should have a unique "key" prop.
Check the render method of CommentBox. See https:// fb . me/react-warning-keys for more information.
in label (created by CommentBox)
in CommentBox
class CommentBox extends React.Component {
render() {
const span = React.createElement('span', { className: "span" }, 'First Component React');
const label = React.createElement('label', { className: "label" }, span);
const brTag = React.createElement('br')
const textBox = React.createElement('input', { className: "input", placeholder: "Hola" });
const upperDiv = React.createElement('div', { key:"upperDivinput", className: "div" }, [label, brTag, textBox]);
let comments = [];
let baseUrl = 'https://localhost:44379';
let endPointComments = '/Posts/GetComments';
baseUrl = baseUrl + endPointComments;
fetch(baseUrl)
.then(response => response.json())
.then(result => {
comments = result;
console.log(result);
});
let commentsParagraph = [];
for (let i = 0; i < comments.length; i++) {
console.log(comments[i].author);
const paragItemTitle = React.createElement('p', { key:'author_${i}' ,className: "p" }, comments[i].author);
const paragraphItemText = React.createElement('p', { key: 'comment_${i}', className: "p" }, comments[i].text);
commentsParagraph.push(paragItemTitle);
commentsParagraph.push(paragraphItemText);
}
const lowerDiv = React.createElement('div', { key:"lowerDivComments", className: 'div' }, commentsParagraph);
const wrapperOuter = React.createElement('div', { key:"wapperDivComments", className:"div" }, [upperDiv, lowerDiv])
return wrapperOuter;
}
}
ReactDOM.render(<CommentBox />, document.getElementById('root'));
Yes, you should use functional components instead of class components. componentWillMount() can then be replaced by the useEffect() hook.
And you should consider using JSX as this is way easier to read then your lengthy createElement() calls. Probably best for you to start with the official React guide.
As I've mentioned in my comment use ` (=backticks) for string literals. And try to avoid using an index as a key as an index is not stable.
Here a small example of a similar component as the one you want to build that uses string literals, a good key, a functional component and an API request to request some data. Click this link to see the dummy data that is returned by the API.
function CommentBox() {
const [comments, setComments] = React.useState([]);
React.useEffect(() => {
const url = "https://gorest.co.in/public/v2/comments";
const response = fetch(url).then(resp => resp.json().then(json => setComments(json)));
}, []);
return (
<ul>
{comments.map((comment) => (
<li>
<span>{`Comment No. ${comment.id} from ${comment.name}`}</span>
<blockquote>{comment.body}</blockquote>
</li>
))}
</ul>
);
}
ReactDOM.render(<CommentBox />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Unfortunately I cannot use async/ await when using React on StackOverflow as I have to tick Babel/ ES2015 within my snippet. Usually I would use an async function in useEffect() and use await instead of chaining then().
The useEffect() hook would then look like this. Both versions result in the same output though.
React.useEffect(async () => {
const url = "https://gorest.co.in/public/v2/comments";
const response = await fetch(url);
const data = await resp.json();
setComments(data);
}, []);
Please note: you would still need to implement some error handling here.
The problem is kind of hidden with this kind of code: [label, brTag, textBox] and [upperDiv, lowerDiv].
Any time you have an array of elements, every element in that array must have a key. In this case, your label, brTag, and textBox need a key.
Also, you never want to do things like API requests inside your render function. Please use the lifecycle hooks like componentDidMount for loading data, and calling setState to trigger a new render. DO NOT call setState inside your render method.
Also, do yourself and everybody else a favor and use JSX. It's compiled a build time (not run time), so it doesn't cost you any performance and is much easier to read (note: this advice is coming from someone who initially resisted JSX). In such case, you could use react fragments and avoid having to define unnecessary keys:
<>
<label>...</label>
<br />
<input ... />
</>

Minimize parameters for createSelector in reselect Reactjs

I am using reselect in my ReactJs code. Here is the code snippet. Due to large file, i am ommitting out unnecessary code from it.
import { createSelector } from 'reselect';
const todoSelector = state => state.todo.todos;
const searchTermSelector = state => state.todo.searchTerm;
const searchViewSelector = state => state.todo.userView
export const filteredTodos = createSelector(
[todoSelector, searchTermSelector, searchViewSelector],
(todos, searchTerm, searchView) => {
return todos.filter(todo => todo.title.match(new RegExp(searchTerm, 'i')));
}
);
As you can notice the paramters for createSelector. As we know createSelector expects 2 arguments: an array of input selector(s) as the 1st argument and a function as the 2nd argument
In this case, array of input selectors is 3. ([todoSelector, searchTermSelector, searchViewSelector])
But in my actual code, array of input selectors are 9. I need to reduce the count from 9 to less than 4 due to sonar issues.
How can i minimize the array of input selectors and still make it work as expected. I search a lot online but i didnt find any ifno related to it. Please any suggestions ?
If you want to reduce the number of arguments per selector functions, you can separate logic into multiple pieces and use createSelector function result as an argument for another createSelector function. Something like this:
const todoSelector = state => state.todo.todos;
const searchTermSelector = state => state.todo.searchTerm;
const searchViewSelector = state => state.todo.userView
export const filteredTodosByTerm = createSelector(
[todoSelector, searchTermSelector],
(todos, searchTerm) => {
return todos.filter(todo => todo.title.match(new RegExp(searchTerm, 'i')));
}
);
export const filteredTodosByView = createSelector(
[filteredTodosByTerm, searchViewSelector],
(todos, searchView) => {
return todos.filter(todo => todo.title.match(new RegExp(searchView, 'i')));
}
);
)

Creating a var referencing a store from redux

I am currently working on creating a var that references a store from redux. I created one but within the render(). I want to avoid that and have it called outside of the render. Here is an example of it. I was recommended on using componentWillMount(), but I am not sure how to use it. Here is a snippet of the code I implemented. Note: It works, but only when I render the data. I am using double JSON.parse since they are strings with \
render() {
var busData= store.getState().bus.bus;
var driverData= store.getState().driver.gdriveras;
var dataReady = false;
if (busData&& driverData) {
dataReady = true;
console.log("========Parsing bus data waterout========");
var bus_data_json = JSON.parse(JSON.parse(busData));
console.log(bus_data_json);
console.log("========Parsing driver data waterout========");
var driver_data_json = JSON.parse(JSON.parse(driverData));
console.log(driver_datat_json);
busDatajson.forEach(elem => {
elem.time = getFormattedDate(elem.time)
});
driverDatajson.forEach(elem => {
elem.time = getFormattedDate(elem.time)
});
...
}
}
Here is an example of react-redux usage that will probably help you.
Don't forget to add StoreProvider to your top three component (often named App).
I warned you about the fact that React and Redux are not meant to be used by beginner javascript developer. You should consider learn about immutability and functional programming.
// ----
const driverReducer = (state, action) => {
switch (action.type) {
// ...
case 'SET_BUS': // I assume the action type
return {
...state,
gdriveras: JSON.parse(action.gdriveras) // parse your data here or even better: when you get the response
}
// ...
}
}
// same for busReducer (or where you get the bus HTTP response)
// you can also format your time properties when you get the HTTP response
// In some other file (YourComponent.js)
class YourComponent extends Component {
render() {
const {
bus,
drivers
} = this.props
if (!bus || !drivers) {
return 'loading...'
}
const formatedBus = bus.map(item => ({
...item,
time: getFormattedDate(item.time)
}))
const formatedDrivers = drivers.map(item => ({
...item,
time: getFormattedDate(item.time)
}))
// return children
}
}
// this add bus & drivers as props to your component
const mapStateToProps = state => ({
bus: state.bus.bus,
drivers: state.driver.gdriveras
})
export default connect(mapStateToProps)(YourComponent)
// you have to add StoreProvider from react-redux, otherwise connect will not be aware of your store

Using branch from recompose

I am refactoring a stateless functional component to use branch and renderComponent from recompose.
The original component looks like this:
const Icon = props => {
const { type, name } = props
let res
if (type === 'font') {
return (<FontIcon name={name} />)
} else if (type === 'svg') {
res = (<SvgIcon glyph={name} />)
}
return res
}
The component with branch looks like this:
const isFont = props => {
const { type } = props
return type === 'font'
}
const FontIconHoC = renderComponent(FontIcon)
const SvgIconHoC = renderComponent(SvgIcon)
const Icon = branch(isFont, FontIconHoC, SvgIconHoC)
Icon.propTypes = {
type: string,
name: string
}
export default Icon
I try and render the component using:
<Icon name='crosshairs' type='font' />
The resulting error looks like this:
invariant.js:44Uncaught Error: Icon(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
branch returns a HOC, which accepts a component and return a component, so branch(...) is a HOC and branch(...)(...) is a component.
In your case, because Icon is not a component but a HOC, so React can't render it. To fix it, you can move SvgIcon out from branch's arguments and apply it to the HOC returned by branch(...), ex:
const Icon = branch(
isFont,
FontIconHoC,
a => a
)(SvgIcon)
We apply an identity function (a => a) to the third argument of branch. You can think of the identity function is also a HOC, which basically just return the component it gets and does nothing more.
Because this pattern is used very often, so the third argument of branch is default to the identity function. As a result, we can omit it and make our code simpler:
const Icon = branch(
isFont,
FontIconHoC
)(SvgIcon)
I've created a jsfiddle for these code. You can try it here.
You can also just use an if statement instead of branch. Consider that you just had some difficulties doing what an if statement does.
Maybe time to reconsider that library ?

Categories

Resources