i was using AOR 1.2.2 for site settings panel.
Tricky part was that those settings can have different types: string,int,bool, array of string, int etc
I managed to do this by connecting record from store and using this code:
const SettingsEdit = (props) => {
return (
<Edit actions={<SettingsEditActions {...props} />} title=
{<SettingsTitle />} {...props}>
<SimpleForm toolbar={<EditToolbar />}>
<TextField source="description" />
<DisabledInput elStyle={{ width: '100%' }} label="Default
value" source="defaultValue" />
{renderCountryValue(props)}
</SimpleForm>
</Edit>
);
};
const renderCountryValue = (prop) => {
const record = prop.record;
if (record) {
if (record.multilang) {
// countryValue will be a dict with locale keys
// TODO Multilang fields temporary disabled in restClient
return null;
}
// countryValue will be single value or array
if (record.schema.type === 'array') {
// countryValue will be single array
if (record.schema.items.type === 'string') {
return <LongTextInput format={v => v.join()} parse={v => v.split(',')} label="Value" source="countryValue" />;
}
if (record.schema.items.type === 'integer') {
return <LongTextInput format={v => v.join()} parse={v => v.split(',')} validate={validateIntegerArray} label="Value" source="countryValue" />;
}
}
// countryValue will be single value
if (record.schema.type === 'string') {
return <TextInput label="Value" source="countryValue" />;
}
if (record.schema.type === 'integer') {
return <NumberInput label="Value" source="countryValue" />;
}
if (record.schema.type === 'boolean') {
return <BooleanInput label="Value" source="countryValue" />;
}
return <LongTextInput label="Value" source="countryValue" />;
}
return <TextInput label="Value" source="countryValue" />;
};
It was working well untill i tried updating AOR to 1.3.1 then it stopped.
What i noticed is that in first render there is no record so it renders default TextInput but on second render when there is record it doesn't rerender this Input into correct type like NumberInput or etc.
I tried to debug it and programm come to place when it should render other Input but nothing happen on screen.
Any ideas or workarounds?
Have faced some issues myself when depending on record in props. I usually then set manual checks for the value and return null when no record is found in props.
Related
I'm building a controlled form with dynamic fields.
The Parent component get data from a redux store and then set state with the values.
I don't want to make it with too much code lines so I turn the dynamic fields into a component.
States stay in the parent component and I use props to pass the handlechange function.
Parent :
function EditAbout(props) {
const [img, setImg] = useState("");
const [body, setBody] = useState(props.about.body);
const [instagram, setInstagram] = useState(props.about.links.instagram);
const [linkedin, setLinkedIn] = useState(props.about.links.linkedin);
const [press, setPress] = useState(props.about.press)
const handleSubmit = (e) => {
// Submit the change to redux
};
// set states with redux store
useEffect(() => {
setBody(props.about.body);
setInstagram(props.about.links.instagram);
setLinkedIn(props.about.links.linkedin);
setPress(props.about.press);
}, []);
const handleChangeChild = (e, index) => {
e.preventDefault();
let articles = press
const {value, name } = e.target
if (name === "title") {
articles[index].title = value;
} else {
articles[index].link = value;
}
setPress(articles)
console.log(articles[index])
}
return (
<Box>
<h1>CHANGE ABOUT ME</h1>
<Input
label="Image"
name="img"
type="file"
variant="outlined"
margin="normal"
onChange={(e) => setImg(e.target.files)}
/>
<Input
label="body"
value={body}
name="body"
onChange={(e) => setBody(e.target.value)}
variant="outlined"
multiline
rowsMax={12}
margin="normal"
/>
<Input
label="instagram"
value={instagram}
name="instagram"
variant="outlined"
margin="normal"
onChange={(e) => setInstagram(e.target.value)}
/>
<Input
label="Linkedin"
value={linkedin}
name="linkedin"
variant="outlined"
margin="normal"
onChange={(e) => setLinkedIn(e.target.value)}
/>
<Child press={press} onChange={handleChangeChild} />
{props.loading ? (
<CircularProgress color="black" />
) : (
<Button onClick={handleSubmit} variant="contained">
Send
</Button>
)}
</Box>
);
}
Child :
function Child(props) {
const { press, onChange } = props;
const inputsMarkup = () =>
press.map((article, index) => (
<div key={`press${index}`} style={{ display: "flex" }}>
<input
name="title"
value={press[index].title}
onChange={(e) => onChange(e, index)}
/>
<input
name="link"
value={press[index].link}
onChange={(e) => onChange(e, index)}
/>
<button>Delete</button>
</div>
));
return (
<div>
<h1>Press :</h1>
{inputsMarkup()}
</div>
);
}
Everything is fine when I'm typing in the Parent inputs. But when I'm using Child fields state update for one character but come back at its previous state right after.
It also doesn't display the character change. I can only see it in the console.
Thanks you in advance for your help
The problem is that you're mutating the state directly. When you create the articles variable (let articles = press) you don't actually create a copy and articles doesn't actually contain the value. It's only a reference to that value, which points to the object’s location in memory.
So when you update articles[index].title in your handleChangeChild function, you're actually changing the press state too. You might think that's fine, but without calling setPress() React will not be aware of the change. So, although the state value is changed, you won't see it because React won't re-render it.
You need to create a copy of the press array using .map() and create a copy of the updated array element. You can find the updated handleChangeChild() below:
const handleChangeChild = (e, index) => {
e.preventDefault();
const { value, name } = e.target;
setPress(
// .map() returns a new array
press.map((item, i) => {
// if the current item is not the one we need to update, just return it
if (i !== index) {
return item;
}
// create a new object by copying the item
const updatedItem = {
...item,
};
// we can safely update the properties now it won't affect the state
if (name === 'title') {
updatedItem.title = value;
} else {
updatedItem.link = value;
}
return updatedItem;
}),
);
};
I have a react component as :
const CustomDatePicker = ({ errors, id, name, control, rules, getValues, minDate, maxDate, placeholder, required, defaultValue, ...rest }) => {
console.log("required 1", name, required);
const inputRef = React.useRef();
const validateField = () => {
console.log("required 2", required, name)
if (required && !getValues(name)) {
return false
}
else if (getValues(name)) {
let dateObj = typeof (getValues(name)) == 'string' ? new Date(getValues(name)) : getValues(name)
return !isNaN(dateObj);
}
else return true;
}
return (
<div className="form-group custom-input-container">
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<Controller
name={name}
margin="normal"
fullWidth
variant="outlined"
onFocus={() => {
if (inputRef.current) {
inputRef.current.focus()
}
}}
defaultValue={defaultValue}
as={<KeyboardDatePicker
inputRef={inputRef}
className="custom-date-col"
fullWidth
autoOk
clearable
variant="inline"
inputVariant="outlined"
placeholder={placeholder}
minDate={minDate}
format="dd-MM-yyyy"
maxDate={maxDate}
/>}
rules={{
validate: validateField
}}
control={control}
errors={errors}
{...rest}
/>
</MuiPickersUtilsProvider>
</div>
);
};
export default CustomDatePicker;
and a parent component uses the above as follows :
export default function Dummy(props) {
const [req,setReq]=useState(false);
return (
<div className="FamilyDetails__emergency-form-row FamilyDetails__three-col-grid">
<CustomDatePicker
name={`marriageDate`}
errors={props.errors}
control={props.control}
maxDate={new Date()}
minDate={getMinDate()}
placeholder={"dateOfMarriageLbl"}
required={req}
defaultValue={new Date()}
/>
</div>
);
}
I use the above components in the form. Initially "required" prop is going to be false then later it will be changed to true. When I submit the form validateField method gets called and the required prop value in console is printed as false whereas the original value is true. Then console printed outside the function prints "require" prop value as true. The value of "required" prop in validateField function is taking the initial value with which component is initially rendered. Please help me through this.
It looks like react-hook-forms caches validation and the issue has been resolved in latest versions. find the discussion here. This has solved my issue.
I converted a class component into a function component using hooks. Currently, I'm struggling to figure out why the checkboxes within this map is not updating with checked value, despite the onChange handler firing, and updating the array as necessary. (The onSubmit also works, and updates the value within the DB properly).
import {
Container,
Typography,
Grid,
Checkbox,
FormControlLabel,
Button
} from "#material-ui/core";
import Select from "react-select";
import localeSelect from "../services/localeSelect";
import {
linkCharactersToGame,
characterLinked,
linkCharacters
} from "../data/locales";
import dbLocale from "../services/dbLocale";
import { LanguageContext } from "../contexts/LanguageContext";
import { UserContext } from "../contexts/UserContext";
import { GameContext } from "../contexts/GameContext";
import { CharacterContext } from "../contexts/CharacterContext";
import { Redirect } from "react-router-dom";
export default function LinkCharacter() {
const { language } = useContext(LanguageContext);
const { user } = useContext(UserContext);
const { games, loading, error, success, connectCharacters } = useContext(
GameContext
);
const { characters } = useContext(CharacterContext);
const [game, setGame] = useState("");
const [selectedCharacters, setSelectedCharacters] = useState([]);
if (!user) {
return <Redirect to="/" />;
}
return (
<section className="link-character">
<Container maxWidth="sm">
<Typography variant="h5">
{localeSelect(language, linkCharactersToGame)}
</Typography>
{error && (
<p className="error">
<span>Error:</span> {error}
</p>
)}
{success && <p>{localeSelect(language, characterLinked)}</p>}
<Select
options={games.map(game => {
return {
label: dbLocale(language, game),
value: game._id
};
})}
onChange={e => {
setGame(e.value);
const selected = [];
const index = games.findIndex(x => x._id === e.value);
games[index].characters.forEach(character => {
selected.push(character._id);
});
setSelectedCharacters(selected);
}}
/>
</Container>
<Container maxWidth="md">
{game !== "" && (
<>
<Grid container spacing={2}>
{characters.map((character, index) => {
return (
<Grid item key={index} md={3} sm={4} xs={6}>
<FormControlLabel
control={
<Checkbox
value={character._id}
onChange={e => {
const index = selectedCharacters.indexOf(
e.target.value
);
if (index === -1) {
selectedCharacters.push(e.target.value);
} else {
selectedCharacters.splice(index, 1);
}
}}
color="primary"
checked={
selectedCharacters.indexOf(character._id) !== -1
}
/>
}
label={dbLocale(language, character)}
/>
</Grid>
);
})}
</Grid>
<Button
variant="contained"
color="primary"
onClick={e => {
e.preventDefault();
connectCharacters(game, selectedCharacters);
}}
>
{localeSelect(language, linkCharacters)}
</Button>
</>
)}
</Container>
</section>
);
}
I feel like there's something I'm missing within Hooks (or there's some sort of issue with Hooks handling something like this). I have been searching and asking around and no one else has been able to figure out this issue as well.
The state returned by [state, setState] = useState([]) is something that you should only be reading from. If you modify it, React won't know that the data has changed and that it needs to re-render. When you need to modify data, you have to use setState, or in your case setSelectedCharacters.
Also, modifying the data by reference might lead to unpredictable results if the array is read elsewhere, later on.
In addition to that, if you give the same value to setState, that the hook returned you in state, React will skip the update entirely. It is not a problem when using numbers or strings, but it becomes one when you use arrays, because the reference (the value React uses to tell if there is a difference) can be the same, when the content might have changed. So you must pass a new array to setState.
With that in mind, your onChange function could look like:
onChange={e => {
const index = selectedCharacters.indexOf(
e.target.value
);
if (index === -1) {
// creating a new array with [], so the original one stays intact
setSelectedCharacters([...selectedCharacters, e.target.value]);
} else {
// Array.filter also creates new array
setSelectedCharacters(selectedCharacters.filter((char, i) => i !== index));
}
}}
Doc is here https://en.reactjs.org/docs/hooks-reference.html#usestate
I have a component which use Picker from 'react-native' and in props I am receiving bool value showDefaultPickerItem to show or hide the Picker.Item. The problem is that doing this way doesn´t work, I receive the error
null is not an object evaluating child props
For sure the list has length grater than 1.
function PickerComponent(props){
const { selectedValue, onValueChange, list, label, valuekey, showDefaultPickerItem } = props;
return (
<Picker
selectedValue={selectedValue}
onValueChange={(value) => onValueChange(value)}
>
{showDefaultPickerItem &&
<Picker.Item label={"Select"} value={undefined} color ={gray}/>
}
{list.map(l => {
return <Picker.Item key={l[valuekey]} label={l[label]} value={l[valuekey]} />
})
}
</Picker>
)
}
I think this code is helpful to you.
function make_list(list, showDefaultPickerItem, valuekey, label) {
let listMap = list.map(l => <Picker.Item key={l[valuekey]} label={l[label]} value={l[valuekey]} />);
if (showDefaultPickerItem) {
listMap.unshift(<Picker.Item key="_default" label={"Select"} value={undefined} color={'gray'} />);
}
return listMap;
}
function PickerComponent(props){
const { selectedValue, onValueChange, list, label, valuekey, showDefaultPickerItem } = props;
return (
<Picker
selectedValue={selectedValue}
onValueChange={(value) => onValueChange(value)}
>
{make_list(list, showDefaultPickerItem, valuekey, label)}
</Picker>
)
}
I'm working on React with Symfony API and when I connect to my app, I've got a role defined by Symfony
It returns this if I'm an admin : ["ROLE_USER", "ROLE_ADMIN"]
It returns this if I'm a moderator : ["ROLE_USER", "ROLE_MODERATOR"]
It returns this if I'm a user : ["ROLE_USER"]
Currently my code is working fine and if I'm a user, it shows the user view, if I'm a moderator it shows the moderator view etc.
So my question is : Is there a better way to create a condition that will render the good component in function of my user role ?
render()
{
let content = "";
if (this.props.auth.user.roles.includes("ROLE_ADMIN")) {
content = <NavAdminDashboard />;
} else if (this.props.auth.user.roles.includes("ROLE_MODERATOR")) {
content = <NavModeratorDashboard />;
} else {
content = <NavUserDashboard />;
}
return (
<Fragment>
{content}
</Fragment>
)
}
I have checked this : Render component based on a variable - reactjs
It is better than my code but it only works if my roles render as string and not as array like my code.
You can achieve this in two ways
The first one is a little cleaner.
render(){
const {roles} = this.props.auth.user;
return (
<React.Fragment>
{ roles.include("ROLE_ADMIN") && <NavAdminDashboard /> }
{ roles.include("ROLE_MODERATOR") && <NavModeratorDashboard /> }
{ !roles.include("ROLE_ADMIN") && !roles.include("ROLE_MODERATOR) && <NavUserDashboard /> }
</React.Fragment>
)
}
You can also do that by creating two methods isAdmin and isModerator:
isAdmin = () => {
return this.props.auth.user.roles.include("ROLE_ADMIN");
}
isModerator = () => {
return this.props.auth.user.roles.include("ROLE_MODERATOR");
}
render() {
return (
<React.Fragment>
{ this.isAdmin() && <NavAdminDashboard /> }
{ this.isModerator() && <NavModeratorDashboard /> }
{ !this.isAdmin() && !this.isModerator() && <NavUserDashboard /> }
</React.Fragment>
)
}
Or you can add a isUser method to check if its only user
isUser = () => {
const {roles} = this.props.auth.user;
return roles.include("ROLE_USER") && roles.length === 1;
}
render() {
return (
<React.Fragment>
{ this.isAdmin() && <NavAdminDashboard /> }
{ this.isModerator() && <NavModeratorDashboard /> }
{ this.isUser() && <NavUserDashboard /> }
</React.Fragment>
)
}
I think your code is fine and doesn't necessary need to change. But I personally move the role logic either to external functions (that can be unit tested) or methods on the component. Eg:
get isAdmin() {
return this.props.roles.include('ADMIN');
}
get isUser() {
return !this.props.roles.some(role => role !== 'USER');
}
render() {
return <>
{this.isAdmin && <Admin />}
{this.isUser && <User />}
</>
}
Another alternative is to move the parsing of roles to a helper function and map the array to props. Eg:
<Component isAdmin={hasAdminRole(roles)} />
Both of these are nicer solutions if you ask me. But in the end, as long as the code works it might be good enough. You can always go back and refactor later.