Données
HTML
Données
HTML
State
XHR
Event handlers
Mount | Update | Unmount |
---|---|---|
constructor() getDerivedStateFromProps() render() componentDidMount() |
getDerivedStateFromProps() shouldComponentUpdate() render() getSnapshotBeforeUpdate() componentDidUpdate() |
componentWillUnmount() |
Déclencher des side effects
Utiliser du state
State |
---|
this.state this.setState() |
export class MessageList extends React.Component<Props> {
componentDidMount() {
this.props.fetchMessages(this.props.userId, this.props.folderId);
}
render() {
return (
<ul>
{this.props.messages.map(message => (
<li key={message.id}>{message.text}</li>
))}
</ul>
);
}
}
Qui voit un bug possible ?
Si props.folderId change (ex navigation de page), on ne va pas chercher la nouvelle liste de messages !
export class MessageList extends React.Component<Props> {
componentDidMount() {
this.props.fetchMessages(this.props.userId, this.props.folderId);
}
componentDidUpdate(prevProps: Props) {
if (prevProps.userId !== this.props.userId || prevProps.folderId !== this.props.folderId) {
this.props.fetchMessages(this.props.userId, this.props.folderId);
}
}
render() {
return (
<ul>
{this.props.messages.map(message => (
<li key={message.id}>{message.text}</li>
))}
</ul>
);
}
}
Code dupliqué ...
const MessageList: React.FunctionComponent<Props> = ({ messages }) => {
const [selectedMessageId, setSelectedMessageId] = useState<number | null>(null);
return (
<ul>
{messages.map(message => (
<li
key={message.id}
className={message.id === selectedMessageId ? 'selected' : ''}
onClick={() => setSelectedMessageId(message.id)}
>
{message.text}
</li>
))}
</ul>
);
};
const [value, setter] = useState<valueType>(initialValue);
const MessageList: React.FunctionComponent<Props> = ({ messages }) => {
const [selectedMessageIds, setSelectedMessageIds] = useState<number[]>([]);
const addToSelection = (newMessageId: number) => {
setSelectedMessageIds([...selectedMessageIds, newMessageId]);
};
const removeFromSelection = (messageIdToRemove: number) => {
setSelectedMessageIds(selectedMessageIds.filter(messageId => messageId !== messageIdToRemove));
};
const toggleSelected = (messageId: number) => {
return selectedMessageIds.includes(messageId)
? removeFromSelection(messageId)
: addToSelection(messageId);
};
return (
<ul>
{messages.map(message => (
<li
key={message.id}
className={selectedMessageIds.includes(message.id) ? 'selected' : ''}
onClick={() => toggleSelected(message.id)}
>
{message.text}
</li>
))}
</ul>
);
};
const MessageList: React.FunctionComponent<Props> = ({
userId,
folderId,
messages,
fetchMessages,
}) => {
useEffect(
() => {
fetchMessages(userId, folderId);
},
[userId, folderId],
);
return (
<ul>
{messages.map(message => (
<li key={message.id}>{message.text}</li>
))}
</ul>
);
};
useEffect(() => {
doStuff();
return () => { undoStuff(); }; // optionnel
}, [trigger1, trigger2]);
useEffect(() => {
doStuff();
return () => { undoStuff(); }; // optionnel
}, [trigger1, trigger2]);
Qu'est-ce que ça fait ?
useEffect(() => {
document.title = 'Liste de messages';
}, []);
Sans dépendance (au mount)
useEffect(() => {
setRenderCount(renderCount + 1);
});
Systématique
Avec cleanup
// Dans un composant de live chat avec une WebSocket
useEffect(() => {
const listener = socket.addEventListener(
'message',
(message) => setMessageList([...messageList, message])
);
return () => {
socket.removeEventListener('message', listener);
}
}, []);
// Dans un formulaire Formik
useEffect(
() => {
if (values.organisationType !== null && values.companyLegalForm) {
if (
values.organisationType.id === ORGANISATION_TYPE_ID.PHYSICAL_PERSON &&
!physicalEntityList.includes(values.companyLegalForm.id)
) {
setFieldValue('companyLegalForm', null);
}
if (
values.organisationType.id === ORGANISATION_TYPE_ID.CORPORATE_ENTITY &&
!corporateEntityList.includes(values.companyLegalForm.id)
) {
setFieldValue('companyLegalForm', null);
}
}
},
[values.organisationType],
);
Effet du menu déroulant "organisationType" sur les options du menu déroulant "companyLegalForm"
class AdministrativeDivisionInput extends React.PureComponent<Props> {
state: State = {
administrativeDivision: {
region: this.props.value ? this.props.value.province.region : undefined,
province: this.props.value ? this.props.value.province : undefined,
commune: this.props.value,
},
provinceList: [],
communeList: [],
};
componentDidMount() {
this.init(this.props);
}
componentWillReceiveProps(nextProps: Props) {
this.init(nextProps);
}
init = (props: Props) => {
const { region, administrativeEntitiesById, value } = props;
const {
regionById,
provinceListByRegionId,
communeListByProvinceId,
} = administrativeEntitiesById;
if (region && !isEmpty(regionById)) {
let regionFromCommune: Region | null = null;
let province: Province | null = null;
let commune: Commune | null = null;
let provinceList: Province[] = [];
let communeList: Commune[] = [];
if (value) {
commune = value;
province = commune.province;
regionFromCommune = regionById[province.region.id];
provinceList = provinceListByRegionId[regionFromCommune.id];
communeList = communeListByProvinceId[province.id];
} else {
regionFromCommune = regionById[region.id];
provinceList = provinceListByRegionId[regionFromCommune.id];
communeList = [];
}
this.setState((state: State) => ({
...state,
administrativeDivision: {
region: regionFromCommune,
province,
commune,
},
provinceList,
communeList,
}));
}
};
handleChange = (
event: React.ChangeEvent<{ name?: string | undefined; value: unknown }>,
type: string,
) => {
const { setFieldValue, field, administrativeEntitiesById } = this.props;
const {
regionById,
provinceById,
communeById,
provinceListByRegionId,
communeListByProvinceId,
} = administrativeEntitiesById;
const { administrativeDivision } = this.state;
const entityId = event.target.value as string;
switch (type) {
case 'region':
const prevRegion = administrativeDivision.region ? administrativeDivision.region : null;
if (prevRegion === null || entityId !== prevRegion.id.toString()) {
this.setState((state: State) => ({
...state,
administrativeDivision: {
region: regionById[entityId],
province: null,
commune: null,
},
provinceList: provinceListByRegionId[entityId],
communeList: [],
}));
}
break;
case 'province':
const prevProvince = administrativeDivision.province
? administrativeDivision.province
: null;
if (prevProvince === null || entityId !== prevProvince.id.toString()) {
this.setState((state: State) => ({
...state,
administrativeDivision: {
...administrativeDivision,
province: provinceById[entityId],
commune: null,
},
communeList: communeListByProvinceId[entityId],
}));
}
break;
case 'commune':
const prevCommune = administrativeDivision.commune ? administrativeDivision.commune : null;
if (prevCommune === null || entityId !== prevCommune.id.toString()) {
const commune = communeById[entityId];
this.setState((state: State) => ({
...state,
administrativeDivision: {
...administrativeDivision,
commune,
},
}));
setFieldValue(field.name, commune);
}
break;
}
};
render() {
const { intl, regionList, field, value, region, classes, error, showRegion } = this.props;
const { administrativeDivision, provinceList, communeList } = this.state;
return (
<>
{(!region || showRegion) && (
<FormControl
classes={{
root: classes.container,
}}
disabled={!regionList || regionList.length === 0}
>
<InputLabel classes={{ root: classes.label }}>
{intl.formatMessage({ id: 'common.administrative.division.region' })}
</InputLabel>
<Select
classes={{ root: classes.content }}
onChange={event => this.handleChange(event, 'region')}
value={administrativeDivision.region ? administrativeDivision.region.id : ''}
inputProps={{
name: `${field.name}-region`,
}}
>
{regionList.map((regionOption: Region) => (
<MenuItem key={regionOption.id} value={regionOption.id}>
{regionOption.name}
</MenuItem>
))}
</Select>
</FormControl>
)}
<FormControl
classes={{
root: classes.container,
}}
disabled={!provinceList || provinceList.length === 0}
>
<InputLabel classes={{ root: classes.label }}>
{intl.formatMessage({ id: 'common.administrative.division.province' })}
</InputLabel>
<Select
classes={{ root: classes.content }}
onChange={event => this.handleChange(event, 'province')}
value={administrativeDivision.province ? administrativeDivision.province.id : ''}
inputProps={{
name: `${field.name}-province`,
}}
>
{provinceList.map((province: Province) => (
<MenuItem key={province.id} value={province.id}>
{province.name}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl
classes={{
root: classes.container,
}}
disabled={!communeList || communeList.length === 0}
>
<InputLabel classes={{ root: error ? classes.labelError : classes.label }}>
{intl.formatMessage({ id: 'common.administrative.division.commune' })}
</InputLabel>
<Select
classes={{ root: classes.content }}
onChange={event => this.handleChange(event, 'commune')}
value={value ? value.id : ''}
inputProps={{
name: field.name,
}}
>
{communeList.map((commune: Commune) => (
<MenuItem key={commune.id} value={commune.id}>
{commune.name}
</MenuItem>
))}
</Select>
</FormControl>
</>
);
}
}
export default withStyles(AdministrativeDivisionStyle)(AdministrativeDivisionInput);
Règle | Raison |
---|---|
Toujours appeler les hooks dans la fonction render, pas dans des boucles ou des conditions. | React a besoin que les hooks soient toujours appelés dans le même ordre. |
Toujours appeler les hooks dans le composant ou dans un hook custom. | React sauvegarde l'état des hooks sur le composant. |
Pas de hooks dans une classe. | Ne pas mélanger la mémoire des hooks et le state de la classe. |
Convention de nommage : un hook commence par "use". Exemple : useEffectOfNationalityOnDocumentType | Pour reconnaître un hook au premier coup d'oeil. |