Passing directive as props - solid-js

For example I have a simple directive like this:
export const inputLogger = (el: HTMLInputElement) => {
const onInput = (e: Event) => {
console.log((e.currentTarget as HTMLInputElement).value)
}
el.addEventListener("input", onInput);
};
and my component looks like this
const CustomInput: Component<JSX.InputHTMLAttributes<HTMLInputElement>> = (props) => {
return <input {...props} />
}
function Counter() {
return (
<>
<CustomInput use:inputLogger /> {/* directive is not being used */}
<br />
<input use:inputLogger /> {/* directive works properly */}
</>
);
}
Seems like inputLogger directive doesn't work properly when being passed as a prop to other component, but works fine when passed to a native input element.
Am I doing something wrong? Is there a workaround for this or is this a limitation from Solid.js?
Playground Example

Unfortunately, in Solid, directives can only be applied to native elements, not to components. See here: https://www.solidjs.com/tutorial/bindings_directives.
You can also use a pattern like this, but you no longer can rely on spreading props.
const CustomInput: Component<JSX.InputHTMLAttributes<HTMLInputElement>> = (
props
) => {
return <input {...props} ref={props["use:inputLogger"]} />;
};
function Counter() {
return (
<>
<CustomInput use:inputLogger={inputLogger} />
</>
);
}
https://playground.solidjs.com/?hash=-51759678&version=1.4.1

Related

How to update Form options after submitting using useReducer

i´m new at programming..
I'm coding a form that has a Select list of Options, which come from an array of objects (i named it initialState for useReducer use).
Now i have to use the useReducer hook to update those options when the form is submitted. The submitted option has to be eliminated after submitting.
I just started coding the hook, but i really don´t know what to do...
The app is rendering OK, and the "placeholder" initial state is showing the first option value. I'm okay with that.
Can you help me plase? Thanks!
import { useState, useReducer } from "react";
const handleSubmit = (e) => {
e.preventDefault();
}
const BookingForm = () => {
const initialState = [
{value: "17:00"},
{value: "17:30"},
{value: "18:00"},
{value: "18:30"},
{value: "19:00"},
{value: "19:30"}]
const reducer = (state, action) => {
?????? }
const [state, dispatch] = useReducer(reducer, initialState);
const [option, setOption] = useState({initialState});
return(
<div className="form_div">
<form className="form" onSubmit={handleSubmit}>
<label htmlFor="res-time" className="label">Choose time
<select id="res-time " className="input"
value={option}
onChange={(e) => setOption(e.target.value)}>
{initialState.map(item => {
return (<option key={item.value} value={item.value}>{item.value}</option>);
})}
</select>
</label>
<input type="submit" value="Make Your reservation" className="button" onClick={() => dispatch({type: "selected option"})}/>
</form>
export default BookingForm;

material-ui / How to style an HOC using 'withStyles()'?

My HOC :
const withPaper = Component => props => (
<Paper>
<Component {...props} />
</Paper>
);
export default withPaper;
I want to style 'Paper' component using withStyles():
const styles = theme => ({
root: {
backgroundColor: 'green'
}
});
const withPaper = ?? => ?? => (
<Paper className={classes.root}>
<Component {...props} />
</Paper>
);
export default withStyles(styles)(withPaper);
How can I inject classes prop this case?
My simple idea Component => ({classes, ...props}) => logs error.
TypeError: Cannot call a class as a function
Answering my own question.
I ignored the returns of HOC. It returns 'Component' instead of 'React Element'.
I'm not sure, but I think this is the reason that I couldn't inject classes from outside of HOC.
My solution that works well - styling inside of HOC:
const withPaper = Component => {
const WithPaper = ({ classes, ...props }) => (
<Paper className={classes.root}>
<Component {...props} />
</Paper>
);
const styles = theme => ({
root: {
backgroundColor: 'green'
}
});
return withStyles(styles)(WithPaper);
};
export default withPaper;
FYI, TypeScript users can refer to Rahel's answer.
I am in the process of learning Material-UI and TypeScript myself, and I was actually struggling with the very same thing :-) Apologies if you were looking for a JS solution, but the types being explicit might actually help:
import * as React from "react";
import createStyles from "#material-ui/core/styles/createStyles";
import { WithStyles } from "#material-ui/core";
import Paper from "#material-ui/core/Paper/Paper";
import { compose } from "recompose";
import withStyles from "#material-ui/core/styles/withStyles";
const styles = createStyles({
root: {
backgroundColor: "green"
}
});
type WrapperProps = WithStyles<typeof styles>;
const withPaper = <P extends {}>(Component: React.ComponentType<P>) => {
type Props = P & WrapperProps;
return (props: Props) => {
return (
<Paper className={props.classes.root}>
<Component {...props} />
</Paper>
);
};
};
export default compose(withStyles(styles), withPaper);
Note the usage of recompose, to compose your higher-order components. If you mind this library-dependency, you can also do without:
export default (component: React.ComponentType) => withStyles(styles)(withPaper(component));

Accessing specific element in hyperHTML template repeater

Is there a way to access a specific element in a wire map?
render() {
hyper(this.shadowRoot)`
<style>${css}</style>
<container>
${this.referenceImages.map(image => wire(image)`
<cell>
<inner-cell class="${this.returnClass()}">
</inner-cell>
</cell>
`)}
</container>
`;
}
How would I access the node in returnClass()?
Is there a better way to do what I want by using wire ids and weak references?
Depending on how / why you want to access it, you could store a reference and use <HTMLElement>.querySelector()
render() {
hyper(this.shadowRoot)`
<style>${css}</style>
<container>
${this.referenceImages.map(image => {
const el = wire(image)`
<cell>
<inner-cell class="${this.returnClass()}">
</inner-cell>
</cell>
`;
el.querySelector('inner-cell');
...
return el;
})}
</container>
`;
}

Autocomplete - How can I set a default value?

Does anyone know how to add a default value to the Autocomplete component?
The component have a dataSource, and I'd like to load the page with a specific item already selected(e.g. fill the text field with the selected item's text and it's value already set).
Does anyone knows how? Big thanks for any help <3
You can achieve this by setting the appropriate state in componentDidMount, for example:
componentDidMount() {
// load your items into your autocomplete
// set your default selected item
this.setState({ allItems: [itemYouWantToSet], selectedItem: item.name, selectedItemId: item.id }
}
render() {
return (
<Autocomplete
value={this.state.selectedItem}
items={this.state.allItems}
getItemValue={(item) => item.name}
onSelect={(value, item) => {
this.setState({ selectedItem: value, selectedItemId: value, allItems: [item] });
}}
/>
)
}
Then your item is correctly selected from the list of available options when it loads.
I tried all the above solutions and nothing worked. Perhaps the API has changed since then.
I finally figured out a solution. It's not so elegant, but in principle it makes sense. In my case the options are objects. I just had to set the "value" prop using the exact item from my options array. This way componentDidMount and getOptionSelected aren't needed.
Autocomplete is wrapped inside another component in our case. This is the main code:
class CTAutoComplete extends React.PureComponent {
getSelectedItem(){
const item = this.props.options.find((opt)=>{
if (opt.value == this.props.selectedValue)
return opt;
})
return item || {};
}
render() {
return (
<Autocomplete
id={this.props.id}
className={this.props.className}
style={{ width: '100%' }}
options={this.props.options}
getOptionLabel={this.props.getOptionLabel}
renderInput={params => (
<TextField {...params} label={this.props.label} variant="outlined" />
)}
onChange={this.props.onChange}
value={this.getSelectedItem()}
/>
);
}
}
IMPORTANT: When setting "value", you have to make sure to put the null case " || {} ", otherwise React complains you are changing from an uncontrolled to controlled component.
you can provide the defaultValue prop for AutoComplete.
<Autocomplete
multiple
id="tags-outlined"
options={this.state.categories}
getOptionLabel={(option) => option.category_name}
onChange={this.handleAutocomplete}
defaultValue={'yourDefaultStringValue'} //put your default value here. It should be an object of the categories array.
filterSelectedOptions
renderInput={(params) => (
<TextField
fullWidth
{...params}
variant="outlined"
label="Add Categories"
placeholder="Category"
required
/>
}
/>
This approach works for me (using hooks):
First of all define the options you need in a variable:
const genderOptions = [{ label: 'M' }, { label: 'V' }];
Second you can define a hook to store the selected value (for example store it in session storage for when the page refreshes, or use useState directly):
const age = useSessionStorage('age', '');
Next you can define your Autocomplete as follows (notice the default values in value and getOptionLabel, if you omit those you'll get those controlled to uncontrolled warnings):
<Autocomplete
id="id"
options={ageOptions}
getOptionLabel={option => option.label || ""}
value={ageOptions.find(v => v.label === age[0]) || {}} // since we have objects in our options array, this needs to be a object as well
onInputChange={(_, v) => age[1](v)}
renderInput={params => (
<TextField {...params} label="Leeftijd" variant="outlined" />
)}
/>
It is tricky specially in case of you are using along with filter option which load API on every filter. I was able to load initial value by setting up within state and onInputChange option.
Below is code that work for me or click below link for full working demo.
https://codesandbox.io/s/smoosh-brook-xgpkq?fontsize=14&hidenavigation=1&theme=dark
import React, { useState, useEffect } from "react";
import TextField from "#material-ui/core/TextField";
import Typography from "#material-ui/core/Typography";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function CreateEditStrategy({ match }) {
const [user, setUser] = useState({
_id: "32778",
name: "Magic User's Club!"
});
const [filter, setFilter] = useState("");
const [users, setUsers] = useState([]);
const [openAutoComplete, setOpenAutoComplete] = React.useState(false);
useEffect(() => {
(async () => {
//Will not filter anything for testing purpose
const response = await fetch(
`https://api.tvmaze.com/search/shows?q=${filter}`
);
const shows = await response.json();
setUsers(
shows.map((a, i) => {
return { _id: a.show.id, name: a.show.name };
})
);
})();
}, [filter]);
return (
<div>
<Typography variant="h6">Autocomplete</Typography>
<Autocomplete
open={openAutoComplete}
onOpen={() => setOpenAutoComplete(true)}
value={user}
inputValue={filter}
onClose={() => setOpenAutoComplete(false)}
onChange={(event, user) => {
if (user) setUser(user);
else setUser({ _id: "", name: "" });
}}
onInputChange={(event, newInputValue) => setFilter(newInputValue)}
getOptionSelected={(option, value) => option.name === value.name}
getOptionLabel={(option) => option.name}
options={users}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
InputProps={{
...params.InputProps
}}
/>
)}
/>
</div>
);
}
Call your component like this
<SelectCountryAutosuggest searchText="My Default Value" />
Make sure you apply the default value to state on class load
class SelectCountryAutosuggest extends React.Component {
state = {
value: this.props.searchText, //apply default value instead of ''
suggestions: [],
};
...
}
The api docs suggest the best approach in the current version (June 2022) is to use value and isOptionEqualToValue.
So for example, if I have a list of users and am choosing which user this thing is assigned to:
const [assignedTo, setAssignedTo] = useState(initialOption);
return (<Autocomplete
options={users.map((i) => ({
label: i.name,
value: i._id,
}))}
isOptionEqualToValue={(o, v) => o.value === v.id}
value={assignedTo}
onChange={(evt, val) => setAssignedTo(val)}
renderInput={(params) => (
<TextField {...params} label="Assigned To" />
)}
/>);
We can setup initial value through value property of Autocomplete component
<Autocomplete
fullWidth={true}
label={'Location'}
margin={'noraml'}
multiple={false}
name={'location'}
getOptionSelected={useCallback((option, value) => option.value === value.value)}
value={formValues.location === '' ? {label: ''} : {label: formValues.location}}
options={location}
ref={locationRef}
onChange={useCallback((e, v) => handleInputChange(e, v))}
/>
You can use the searchText property.
<AutoComplete searchText="example" ... />
Try this...
componentWillReceiveProps(nextProps) {
let value = nextProps.value
if (value) {
this.setState({
value: value
})
}
}
onUpdateInput worked for me - for anyone looking through all this as I was
Have you tried setting the searchText prop dynamically? You can pass the value you want to set to the Autocomplete component as the searchText prop. Something like,
<Autocomplete
searchText={this.state.input.name} // usually value={this.state.input.name}
/>
By default, it'll have the initial value set into the TextField of the Autocomplete component but when the user makes any modifications, it calls up the Autocomplete options depending on the dataSource prop.

How to set focus to a materialUI TextField?

How can I set the focus on a material-ui TextField component?
componentDidMount() {
ReactDom.findDomNode(this.refs.myControl).focus()
}
I have tried above code, but it does not work :(
For React 16.8.6, you should use the inputRef property of TextField to set focus. I tried ref property but it doesn't work.
<TextField
inputRef={input => input && input.focus()}
/>
Material-ui doc says:
inputRef: Use this property to pass a ref callback to the native input component.
You can use the autoFocus attribute.
<TextField value="some value" autoFocus />
autoFocus was also not working for me, perhaps since this is a component that's not mounted when the top-level component loads. I had to do something a lot more convoluted to get it to work:
function AutoFocusTextField(props) {
const inputRef = React.useRef();
React.useEffect(() => {
const timeout = setTimeout(() => {
inputRef.current.focus();
}, 100);
return () => {
clearTimeout(timeout);
};
}, []);
return <TextField inputRef={inputRef} {...props} />;
}
Note that for some reason it does not work without the setTimeout. For more info see https://github.com/callemall/material-ui/issues/1594.
If you are using material-ui TextField and react functional component, you can pass inputRef in your TextField component. The trick here is the if condition if(input != null).
<TextField
variant="filled"
inputRef={(input) => {
if(input != null) {
input.focus();
}
}}
/>
Here is an working example for you. CodeSandBox- Material-ui-TextFieldFocus
add this propery to your TextField component :
inputRef={(input) => input?.focus()}
This will focus the component every time it renders. Other solutions I tried only focus the element an initial time.
const inputRef = React.useRef<HTMLInputElement>();
useEffect(() => {
inputRef.current?.focus();
}, [inputRef.current]);
const setTextInputRef = (element: HTMLInputElement) => {
inputRef.current = element;
};
return (
<TextField
inputRef={setTextInputRef}
/>
const handleClick = () => {
inputRef.current.firstChild.focus();
inputRef.current.firstChild.placeholder = '';
}
<InputBase
value={value}
ref={inputRef}
placeholder="search" />
<Button onClick={handleClick}>Click</Button>
This code is actually good, but has a drawback, on every render it's going to create a new function. It easily can be solved using useCallback
<TextField
inputRef={input => input && input.focus()}
/>
Should be
const callbackRef = useCallback((inputElement) => {
if (inputElement) {
inputElement.focus();
}
}, []);
...
<TextField
inputRef={callbackRef}
/>
useRef hook simple example:
const focusMe_Ref = useRef(null); // 1. create
useEffect(() => {
focusMe_Ref.current.focus(); // 2. startup
}, []);
...
<TextField
inputRef={focusMe_Ref} // 3. will focused
...
/>
I am using this solution, works for text fields inspired by https://gist.github.com/carpben/de968e377cbac0ffbdefe1ab56237573
const useFocus = (): [any, () => void] => {
const htmlElRef: MutableRefObject<any> = useRef<HTMLDivElement>();
const setFocus = (): void => {
if (!htmlElRef || !htmlElRef.current) return
const div = htmlElRef.current as HTMLDivElement
if (!div) return
const input = div.querySelector("input")
if (input) input.focus()
}
return [htmlElRef, setFocus];
};
export function MyComp() {
const [ref, setFocus] = useFocus()
// use setFocus() to focus the input field
return <Input ref={ref} />
}
I had a similar problem where the input field didn't regain focus after I've modified its contents with external controls (an emoji picker).
I ended up with this brute workaround hook:
const useEnforceFocusTextField = (textFieldRef: React.RefObject<HTMLInputElement>) => {
const [enforcedRerender, setEnforcedRerender] = useState(false);
React.useEffect(() => {
textFieldRef.current?.focus();
const timeout = setTimeout(() => {
textFieldRef.current?.focus();
}, 100);
return () => clearTimeout(timeout);
}, [enforcedRerender]);
return () => setEnforcedRerender((n) => !n);
};
From the outside, you call utilize this hook in the following manner:
const textFieldRef = useRef<HTMLInputElement>(null);
...
// enforceFocus() can be called in any callback to set the focus to the textfield
const enforceFocus = useEnforceFocusTextField(textFieldRef);
...
return <TextField inputRef={textFieldRef} ... />
For a material ui TextField you need to input the props for autoFocus in a inputProps object like this.
<TextField inputProps={{ autoFocus: true }} />
AlienKevin is correct ( pass a ref callback to "TextField.inputProps" ), but you can also save the element reference on your "this" object, so that you can set focus later. Here is an example in Coffeescript:
TextField
inputProps:
ref: (el)=>
if el?
#input_element = el
Button
onClick:=>
#input_element.focus()