How to pass a function to a component using arrow function? - javascript

FYI: I am using ES6 on ReactJS
I have a switcher. I need it to switch to the other side when clicked.
If click on the side that is currently active does nothing.
Here is my sample code
import { useState } from 'react'
const {isLeftButton, setIsLeftButton} = useState(true);
const toggleSwitcher = () => {
setIsLeftButton(!isLeftButton)
}
const home = () => {
...
return (
<CustomSwitcher isLeftButton={isLeftButton} toggleSwitcher={toggleSwitcher} />
)
...
}
export default Home
Here is the code inside the CustomSwitcher
const CustomSwitcher = (isLeftButton, toggleSwitcher) => {
const leftButton = () => {
if (isLeftButton !== true) {
toggleSwitcher()
}
}
const rightButton = (isLeftButton, toggleSwitcher) => {
if (isRightButton === true) {
toggleSwitcher()
}
}
return (
<div>
<CustomButton onClick={LeftButton}>Left Button</CustomButton>
<CustomButton onClick={rightButton }>Right Button</CustomButton>
</div>
)
}
export default CustomSwitcher
However I got this error
TypeError: toggleSwitcheris not a function
12 | const CustomSwitcher = () => {
13 |
14 | if (leftButton !== true) {
> 15 | toggleSwitcher()
| ^ 16 | }
17 | }
18 |
As I understand, when passing a function down to a component. The function is no longer a function.
And I don't think my code is good. If you can think of a better way to do so. Please contribute.

You are not using the correct way to access the props.
Try to replace
const CustomSwitcher = (isLeftButton, toggleSwitcher) => {
with
const CustomSwitcher = ({isLeftButton, toggleSwitcher}) => {

const CustomSwitcher = (isLeftButton, toggleSwitcher) => { ... }
is not the correct way to build a component.
Either use the props object
const CustomSwitcher = (props) => {
// props.toggleSwitcher
}
Or destructure props
cost CustomSwitcher = ({isLeftButton, toggleSwitcher}) => { ... }

You need to use useState inside a functional component. In your case, inside home. Hooks cannot be used at the global level.
Consequently, you need to define toggleSwitcher inside home also.

Related

How to check value of useRef.current.selectionStart in jest test

I need to test the values of useRef.current.selectionStart and useRef.current.selectionEnd once they have been changed on a onKeyDown interaction with an input.
index.tsx
import React, { useRef, useEffect } from 'react'
type Props {
value: string
...aBunchOfProps
}
const SomeComponent: FC<Props> = ({ value, ...aBunchOfProps }) => {
const inputRef = useRef(null)
const [caretPosition, setCaretPosition] = useState<CaretPosition | undefined>()
useEffect(() => {
if (!!caretPosition && !!value) {
let newCaretPosition = caretPosition.currentPosition
const shouldMoveCaretForward =
caretPosition.direction === 1 && value.charAt(caretPosition.currentPosition) === '/'
if (shouldMoveCaretForward) {
newCaretPosition++
}
inputRef.current.selectionStart = newCaretPosition <=== this is the line I want to test
inputRef.current.selectionEnd = newCaretPosition <=== this is the line I want to test
}
}, [caretPosition])
const someFunction = () => {
// calls setCaretPosition with new details
}
return (
...someAdditionalCode
<input
...someAdditionalProps
ref={inputRef}
value={value}
data-testid="input-field"
onKeyDown={() => someFuction()}
/>
...evenMoreCode
)
}
export default SomeComponent
index.test.tsx
describe('SomeComponent tests', () => {
it('should move cursor correctly', () => {
const { getByTestId } = render(<SomeComonent value="12/3" />)
const input = getByTestId('input-field')
fireEvent.keyDown(input, { key: '4' })
// expect(useRef.current.selectionStart).toEqual(5) <==== I want something like this
// expect(useRef.current.selectionEnd).toEqual(5) <==== I want something like this
})
})
Any suggestions would be helpful.
I had checked your code. It May be not possible to check useRef in testcase file. please check shown below image [![enter image description here and also share that document link, so it can be helpful to you.
document link: https://testing-library.com/docs/guiding-principles/

Order of hooks error when rendering different components

React throws the following error when I am trying to render different components
Warning: React has detected a change in the order of Hooks called by GenericDialog. This will lead to bugs and errors if not fixed.
Previous render
Next render
useRef
useRef
useState
useState
useState
useState
useState
useState
useState
useState
useState
useState
useContext
useState
I do agree this would be inappropriate when I would be rendering the same component each time but with different order of hooks. What I am trying to achieve is render a different component each time so it is quite obvious the order of hooks won't be identical.
I have created this GenericDialog component which renders a multistep dialog.
import React, { useRef, useState, useEffect } from 'react';
import { DialogFooterNavigation } from './DialogFooterNavigation';
import { Dialog } from '../../../../Dialog';
import { Subheader } from '../../../../Subheader';
import { Loading } from '../../../../Loading';
export interface FooterConfiguration {
onContinue?: () => Promise<boolean | void>;
isContinueDisabled?: boolean;
continueLabel?: string;
isBackHidden?: boolean;
isCancelHidden?: boolean;
}
export interface HeaderConfiguration {
subheader?: string;
}
export interface DialogStepProps {
setHeaderConfiguration: (config: HeaderConfiguration) => void;
setFooterConfiguration: (config: FooterConfiguration) => void;
}
export type DialogStep = (props: DialogStepProps) => JSX.Element;
interface GenericDialogProps {
isShown: boolean;
hideDialog: () => void;
steps: DialogStep[];
header: string;
}
export const GenericDialog = ({
isShown,
hideDialog,
steps,
header,
}: GenericDialogProps) => {
const buttonRef = useRef(null);
const [step, setStep] = useState<number>(0);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [headerConfiguration, setHeaderConfiguration] = useState<HeaderConfiguration | undefined>(
undefined,
);
const [footerConfiguration, setFooterConfiguration] = useState<FooterConfiguration | undefined>(
undefined,
);
const [loadingMessage, setLoadingMessage] = useState<string>('');
const dialogBody = steps[step]({
setHeaderConfiguration,
setFooterConfiguration,
});
const nextStep = () => {
if (step < steps.length - 1) {
setStep(step + 1);
}
};
const prevStep = () => step > 0 && setStep(step -1);
const isBackPossible = step > 0;
const onBack = () => (isBackPossible || footerConfiguration?.isBackHidden ? undefined : prevStep);
const onContinue = async () => {
setIsLoading(true);
const result = await footerConfiguration?.onContinue?.call(undefined);
setIsLoading(false);
if (result === false) {
return;
}
nextStep();
};
return (
<Dialog isShown={isShown} onHide={hideDialog}>
<div>
{header}
{headerConfiguration?.subheader && (
<Subheader>{headerConfiguration.subheader}</Subheader>
)}
</div>
{isLoading && loadingMessage ? <Loading msg={loadingMessage} /> : dialogBody}
{!isLoading && (
<DialogFooterNavigation
onBack={isBackPossible ? onBack : undefined}
onContinue={onContinue}
isContinueDisabled={footerConfiguration?.isContinueDisabled}
/>
)}
</Dialog>
);
};
const FirstStep = (props: DialogStepProps) => {
// Here I need useContext
const { id, name } = useCustomerContext();
useEffect(() => {
props.setFooterConfiguration({
isContinueDisabled: !id || !name,
})
}, [id, name]);
return (
<>
<div>ID: {id}</div>
<div>Name: {name}</div>
</>
);
};
const SecondStep = (props: DialogStepProps) => {
// Here I don't need useContext but I do need useState
const [inputValue, setInputValue] = useState({});
useEffect(() => {
props.setFooterConfiguration({
isContinueDisabled: !inputValue,
});
}, [inputValue]);
return <input value={inputValue} onChange={(event) => setInputValue(event.target.value)} />;
}
const MyDialogExample = () => {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const steps: DialogStep[] = [
FirstStep,
SecondStep,
];
return (
<>
<button onClick={() => setIsDialogOpen(true)}>Open Dialog</button>
<GenericDialog
isShown={isDialogOpen}
hideDialog={() => setIsDialogOpen(false)}
steps={steps}
header="Dialog example"
/>
</>
);
};
The problem is here:
const dialogBody = steps[step]({
setHeaderConfiguration,
setFooterConfiguration,
});
Try changing it to something like this:
const DialogBody = steps[step];
And then, in your return statement:
{isLoading && loadingMessage ? <Loading msg={loadingMessage} /> : <DialogBody setHeaderConfiguration={setHeaderConfiguration} setFooterConfiguration={setFooterConfiguration} />}
Please note that it can be done differently, like:
const DialogBody = steps[step];
const dialogBody = <DialogBody setHeaderConfiguration={setHeaderConfiguration} setFooterConfiguration={setFooterConfiguration} />;
And keeping your return statement unaltered.
Explanation
Your code isn't entirely wrong though. When working with functional components, there is a subtle difference between an actual component, a hook and a simple function that returns an instantiated component based on some logic. The problem is that you are mixing those three.
You can't manually instantiate a component by calling its corresponding function (just like you can't instantiate a class component by using the new operator). Either you use JSX (like <DialogBody />) or directly use React inner methods (Like React.createElement()). Both alternatives are different from just doing dialogBody(). For example, if you see the compiled JSX code you will note that <DialogBody /> compiles to code that uses React.createElement() and the latter returns a real React element instance containing many special properties and methods.
dialogBody() would work if its only goal was to return an instantiated element (Using one of the methods above) based on some logic. This implies not using any hook along with some other constraints.
Instead, your dialogBody 'function' contains hooks and it acts as a custom hook itself. This is why React complains about hooks execution order. You are executing hooks conditionally.

React Context - State value is not up-to-date inside a function

I have the following context:
import React, { createContext, useState } from "react";
const OtherUsersContext = createContext(null);
export default OtherUsersContext;
export function OtherUsersProvider({ children }) {
const [otherUsers, setOtherUsers] = useState(new Map([]));
const addUser = (userId, userData) => {
setOtherUsers(
(prevOtherUsers) => new Map([...prevOtherUsers, [userId, userData]])
);
};
const updateUser = (userId, userData, merge = true) => {
...
};
const getUser = (userId) => otherUsers.get(userId);
const resetUsers = () => {
setOtherUsers(new Map([]));
};
return (
<OtherUsersContext.Provider
value={{
addUser,
updateUser,
getUser,
resetUsers,
}}
>
{children}
</OtherUsersContext.Provider>
);
}
In my app, when a user signs out, I need to reset this context's map, using the function "resetUsers".
Currently this is working good, but there has no sense to reset the map if it has no values, so I have changed the "resetUsers" function to:
const resetUsers = () => {
if(otherUsers.size) {
setOtherUsers(new Map([]));
}
}
And, this is not working good, because inside resetUsers, otherUsers.size is always 0. Something which disturbs me because outside the function, the value is the correct one...
...
const resetUsers = () => {
console.log(otherUsers.size); // 0
setOtherUsers(new Map([]));
};
console.log(otherUsers.size); // 5
return ( ...
Any ideas?
The functional updates part of the hooks docs. says:
If the new state is computed using the previous state, you can pass a function to setState.
So instead of just passing the new value to your setter, you can pass a function that depends on the previous state.
This means that you can do:
const resetUsers = () => {
setOtherUsers(prevOtherUsers => prevOtherUsers.size ? new Map([]): prevOtherUsers);
}
One tip, if you are not getting the most updated state value inside a function, then wrap it inside an useCallback.
Try this:
const resetUsers = useCallback(() => {
if (otherUsers.size > 0) {
console.log(otherUsers.size); // 5
setOtherUsers(new Map([]));
}
}, [otherUsers]);

React: function comes back as undefined

Summary
I have the following function inside of a functional component which keeps coming back undefined. All of the data inside the function, tableData and subtractedStats are defined and accurate.
This is probably just a small JavaScript I'm making so your help would be greatly appreciated!
Code
This is a functional component below:
const TableComponent = ({ tableData }) => {
formatTableData = () => {
console.log("inside sumDataFormat", tableData);
return tableData.forEach(competitor => {
let subtractedStats = [];
console.log("competitor in", competitor);
for (const i in competitor.startingLifeTimeStats) {
if (competitor.startingLifeTimeStats[i]) {
competitor.stats
? (subtractedStats[i] =
competitor.stats[i] - competitor.startingLifeTimeStats[i])
: (subtractedStats[i] = 0);
}
}
console.log("subtractedStats", subtractedStats);
return subtractedStats;
});
};
useEffect(() => {
console.log("formatTableData", formatTableData());
});
}
Edit:
Can someone help me to what's wrong in this code (how to solve this?) and could briefly explain a functional component
The forEach function doesn't not return anything, it simply iterates over your array, giving you an undefined, the map function could be what you were looking for :
formatTableData = () => {
console.log("inside sumDataFormat", tableData);
return tableData.map(competitor => { // This returns another array where every element is converted by what's returned in the predicate
Functional Component are the most basic kind of React component, defined by the component's (unchanging) props.
Functional Component needs return some JSX code (UI) (can be null too).
Here's an example of most basic Functional Component
const App = () => {
const greeting = 'Hello Function Component!';
return <Headline value={greeting} />;
};
const Headline = ({ value }) => {
return <h1>{value}</h1>;
};
export default App;
Solution Code
Here's the solution of the above example as a Functional Component
This is solution below uses hooks to save data to component's state and also uses lifecycle methods to parse that data on componentDidMount:
const TableComponent = (props: ) => {
const [state, setState] = useState(initialState)
// componentDidUpdate
useEffect(() => {
setState(getData(props.data));
}, []);
// getData() - Parser for data coming inside the functional Component
const getData = (tableData) => {
return tableData.map(competitor => {
return competitor.startingLifeTimeStats.map((item, index) => {
return item && competitor.stats ? competitor.stats[index]-item : 0;
})
})
};
// UI (JSX)
return (
<Text>{JSON.stringify(state)}</Text>
);
}
export default TableComponent;
Try with map sample code as below.
render() {
return (<div>
{this.state.people.map((person, index) => (
<p>Hello, {person.name} from {person.country}!</p>
))}
</div>);
}

Unexpected token, expected , in React component

I have a React component that looks like this
'use strict';
import 'babel-polyfill';
import React from 'react';
import TreeNode from './TreeView';
export default React.createClass({
mapFromApi : function(data) {
const rec = (tree) => tree.reduce((x,y) => {
const newObj = {
"id" : y["#id"],
"name" : y["rdfs:label"]
};
if (y.hasOwnProperty("options")) {
newObj.children = rec(y.options, []);
}
if (y.hasOwnProperty("children")) {
newObj.children = rec(y.children, []);
}
x.push(newObj);
return x;
}, []);
let t = rec(data);
return t;
},
render: function (data) {
let tree1 = this.mapFromApi(this.props.data.properties).map(child => {
return <TreeNode key={child['id']} data={child}/>;
}.bind(this));
return (
<div className='vocab'>
<h4>vocab1</h4>
<div className='accordion'>
{tree1}
</div>
</div>
);
}
});
When I run this I get an error on the bind keyword
SyntaxError: App.jsx: Unexpected token, expected , (56:5)
54 | let tree1 = this.mapFromApi(this.props.data.properties).map(child => {
55 | return <TreeNode key={child['id']} data={child}/>;
> 56 | }.bind(this));
| ^
57 |
58 | return (
59 | <div className='vocab'>
I'm not sure if this has to do with my Babel setup and I'm very confused about the whole es version situation.
Can some one help me resolve this? Many thanks.
If you simplify the way this code is laid out, these kind of syntax errors should be easier to identify.
let { data } = this.props;
this
.mapFromApi(data.properties)
.map(child => <TreeNode key={child.id} data={child} />)
The arrow function is already going to .bind(this) for you, so you can just omit that.
You shouldn't need to use .bind(this) because you are using the ES6 arrow function => and that will reference the correct this
I would write some thing like this
const {data} = this.props;
let tree1 = this.mapFromApi(data.properties).map(child => {
return <TreeNode key={child['id']} data={child}/>;
});

Categories