Two instances of the same element seem to share state - reactjs

In my React application I have a Collapsible component that I use more than once, like so:
const [openFAQ, setOpenFAQ] = React.useState("");
const handleFAQClick = (question: string) => {
if (question === openFAQ) {
setOpenFAQ("");
} else {
setOpenFAQ(question);
}
};
return ({
FAQS.map(({ question, answer }, index) => (
<Collapsible
key={index}
title={question}
open={openFAQ === question}
onClick={() => handleFAQClick(question)}
>
{answer}
</Collapsible>
))
})
And the Collapsible element accepts open as a prop and does not have own state:
export const Collapsible = ({
title,
open,
children,
onClick,
...props
}: Props) => {
return (
<Container {...props}>
<Toggle open={open} />
<Title onClick={onClick}>{title}</Title>
<Content>
<InnerContent>{children}</InnerContent>
</Content>
</Container>
);
};
However, when I click on the second Collapsible, the first one opens... I can't figure out why.
A working example is available in a sandbox here.

You will need to ensure that the label and id for each checkbox is the same. Here's a working example
But if you're trying to implement an accordion style, you may need another approach.

on Collapsible.tsx line 36, the input id is set the same for both the Collapsibles. you need to make them different from each other and the problem would be solved.

One thing is that you have the same id which is wrong BUT it can still work. Just change the checkbox input from 'defaultChecked={...}' to 'checked={...}'.
The reason is that, if you use defaultSomething - it tells react that even if this value will be changed - do not change this value in the DOM - https://reactjs.org/docs/uncontrolled-components.html#default-values
Changing the value of defaultValue attribute after a component has mounted will not cause any update of the value in the DOM

Related

How to create and render components inside a Vue component like in React?

In React, you can do the following:
export const Button = ({ icon, children, ...props }) => {
const Icon = () => (
icon ? <FontAwesomeIcon icon={["fal", "check"]} className={iconPositionClass}/> : null
);
return (
<BsButton children={children} {...props}>
{iconPosition === "left"
? <><Icon/>{children}</>
: <>{children}<Icon/></>
}
</BsButton>
);
};
I can create another component inside the first one, in this case, <Icon />, and then conditionally render it inside the first one, <Button />.
With Vue, however, it's not like that, because there is no return. There's just a template tag, and while you can create a function in the <script> tag, that function cannot return html.
I know that I can re-create the above code using v-if and v-else for the actual element that I want to be rendered, but that just means that I have to repeat the code inside the <template>. It feels too repetitive.
Is there a way to re-create the React way of doing it in Vue?

`Popover` (as React component) with `OverlayTrigger`

I'm trying to create rich React component as popover content.
If I use example with simple const popover (https://react-bootstrap.netlify.app/components/overlays/#examples-1) everything works fine.
Problem
But custom react component fails to position itself. It appears on top left of the screen
const MyPopover = React.forwardRef((props, ref) => {
return <Popover ref={ref} placement={"bottom"}>
<Popover.Header as="h3">
<Form.Control></Form.Control>
</Popover.Header>
<Popover.Body>
<strong>Holy guacamole!</strong> Check this info.
</Popover.Body>
</Popover>
})
const PopoverChooser = ({children, container}) => {
const _refTarget = useRef(null)
return <>
<OverlayTrigger
trigger="click"
placement="bottom"
overlay={<MyPopover ref={_refTarget}/>}
target={_refTarget.current}
>
{children}
</OverlayTrigger>
</>
}
export default PopoverChooser;
As you can see, I'v tried to use ref's, but it's doesn't help.
Question
How can it link popover to target button (in image as dropdown button and in code as {children}).
Or should I position MyPopover manually (by checking button ID and assigning position.. etc.?)
Completely different approach to dig in..?
Your approach to forward the ref was right. What you actually forgot is to also inject props. According to the documentation:
The and components do not position themselves.
Instead the (or ) components, inject ref and
style props.
https://react-bootstrap.netlify.app/components/overlays/#overview
So what you need to do is to spread the props like this:
const MyPopover = React.forwardRef((props, ref) => {
return (
<Popover ref={ref} {...props}>
https://codesandbox.io/s/trusting-sid-0050g9

Forward multiple ref to react children

I have seen some articles about forwardRef, however couldn't be able to find an example that have a specific approach to it, basically when we are dealing with the children props of React and we need to forward multiple ref to this children props.
I will give an example to clarify any doubt.
Let image we have a parent component List, which will look like it:
const List = ({ children }) => (
<div>
{children}
</div>
);
And we have its children component Tab:
const Tab = ({ children }) => (
<div>
{children}
</div>
);
They are being used like that:
<List>
<Tab />
<Tab />
<Tab />
</List>
Therefore my question is, how I would be able to create multiple refs at List, be able to forward them to Tab, properly set them at each Tab, and them finally get its reference at List to work with.
If there still any doubts I'm happy to clarify.
I guess you can use something like React.cloneElement and pass the references as props to the children.
{React.Children.map(children, childElement =>
React.cloneElement(childElement)
)}
This is the current answer I came with:
const tabReferences = React.useRef<HTMLDivElement>([]);
...
{React.Children.map(children, (child, index) => {
return React.cloneElement(child, {
ref: tabReferences.current[index],
});
})}
Thanks Lucas!

Filtering array of child components

In the parent component, I have a state array of child components:
const [myList, setMyList] = useState([]);
setMyList([
<MyComponent color='blue' />
<MyComponent color='blue1' />
<MyComponent color='blue2' />
<MyComponent color='red' />
])
I display these later on with
myList.map(item => item)
I sometimes might have a large number of items in the list and would like to have a search box that does myList.filter() on the name in the input box.
I have something like this
const itemsToHide = myList.filter(item => !item.props.color.includes(e.target.value));
This correctly gives me the items that do not match the search.
Is there a way to now set display to none to these items? Or hide them in another way that will not dismount them since when i clear the search box i want them to still be there.
Is there a reason to have them in another array? Just checking the condition inside the renderer might be easier. Either wrap your Component in another div
myList.map((item) => {
const isVisible = item.props.name.includes(searchTerm);
return (
<div key={item.props.id} style={{display: `${isVisible ? 'block' : 'none'}`>
{item}
</div>
)
}
or pass a prop and render your component in the same manner. Don't forget to add a unique key to all the divs though.
Just render the items that satisfy the condition:
myList
.filter(item => item.name.includes(searchText))
.map((item) =>
(<div key={item.props.id}>
{item}
</div>
))

Using Stateful React classes in typescipt

I am trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.
Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;
export default withStyles(useStyles)(HeaderBar)
Which exports the class separately with the Styles(withStyles(useStyles) useStyles as the defined styles) And the class(HeaderBar). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!
Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7
And where i try to call it: https://pastebin.com/PVxhKFHJ
My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.
Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).
What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.
You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.
import Button, {ButtonProps as MaterialButtonProps} from "#material-ui/core/Button";
type ButtonState = (button: HTMLButtonElement) => void;
type BarButtonProps = {
label: string;
callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>
const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
return (
<Button
color="inherit" // place first so it can be overwritten by props
onClick={(e) => callback(e.currentTarget)}
{...props}
>
{label}
</Button>
);
};
Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen. If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).
Since it's now a function component, we can get the CSS classes from a material-ui hook.
const useStyles = makeStyles({
root: {
flexGrow: 1
},
menuButton: {
marginRight: 0
},
title: {
flexGrow: 1
}
});
const HeaderBar: FunctionComponent = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<HeaderMenu classes={classes} />
<Typography variant="h6" className={classes.title}>
<BarButton
callback={() => renderModule(<HomePage />)}
style={{ color: "white" }}
label="Sundt Memes"
/>
</Typography>
{children}
</Toolbar>
</AppBar>
</div>
);
};
Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.
I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.
I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule, we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule.
interface LayoutProp {
handle: ReactElement<any, any>;
}
export default function Layout(props: LayoutProp) {
// use a state to respond to an asynchronous response from AuthVerifier
// could start with a third state of null or undefined when we haven't gotten a response yet
const [isLoggedIn, setIsLoggedIn] = useState(false);
// You might want to put this inside a useEffect but I'm not sure when this
// needs to be re-run. On every re-render or just once?
AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));
return (
<div>
<HeaderBar>
{isLoggedIn ? (
<BarButton
label="Log Out"
callback={() => new CookieManager("session").setCookie("")}
/>
) : (
<>
<BarButton
label="Log In"
callback={() => renderModule(<LogInPage />)}
/>
<BarButton
label="Sign Up"
callback={() => renderModule(<SignUpPage />)}
/>
</>
)}
</HeaderBar>
{props.handle}
</div>
);
}
I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.

Resources