1651780560
nodecg-beam
Hook into Beam Events and emit them to NodeCG Compatible channels.
This is a NodeCG 0.8 bundle.
TODO
For events that happen whilst the bundle is offline the Beam API can be pulled and compared to the internal data store.
Installation
nodecg/bundles/nodecg-beam
nodecg/cfg/
called nodecg-beam.json
{
"channels":["ChannelName"]
}
Use
This module does nothing by itself. To use it for alerts and other media you need to listen to the events it outputs.
Checkout prime-alerts for a basic example
nodecg.listenFor('follow', 'nodecg-beam', function(data) {
//Snazzy Alerts
}
{
"name":"username",
"channel":"channel",
"ts":3123123123123
}
nodecg.listenFor('update', 'nodecg-beam', function(data) {
//Snazzy Alerts
}
This event is mostly parroted from the underlying Beam layer. Use them if they are useful!
Author: ProbablePrime
Source Code: https://github.com/ProbablePrime/nodecg-mixer
License: GPL-2.0 License
1651748400
Los formularios son una parte esencial de la mayoría de las aplicaciones. Hay muchos paquetes disponibles en los diferentes paquetes para crear formularios. En este artículo, crearemos un formulario de varios pasos utilizando useState
Hook sin el uso de paquetes de terceros y logrando la misma funcionalidad.
La principal prioridad del tutorial es analizar la lógica de un formulario de varios pasos. Nos saltaremos la mayor parte de la parte de estilo, así que si te quedas atascado en algún lugar, echa un vistazo a este repositorio .
Instalemos un nuevo proyecto Next.js ingresando este comando:
npx create-next-app mulitstep-form
Si lo prefiere, un proyecto React también funcionaría perfectamente bien. Para instalar un nuevo proyecto de React, ingrese lo siguiente:
npx create-react-app multistep-form
El estilo es opcional, pero si desea seguirnos, usaremos Mantine CSS para este proyecto.
Instalar Mantine CSS
npm install @mantine/core @mantine/next @mantine/hooks
Siga la guía de configuración de Next.js aquí .
Next, let’s build some components for each step in our multi-step form.
Primero, construiremos el Form
componente, que manejará la mayor parte de nuestra lógica de formulario. Continúe y cree un nuevo archivo en ./components/Form.js
.
import { Box, Button, Title } from '@mantine/core';
function Form() {
return (
<Box
sx={boxStyle}
>
<Title
sx={{
textAlign: 'center',
}}
order={2}
>
Hey there!
</Title>
{/* Steps */}
<Button>Submit</Button>
</Box>
);
}
export default Form;
(Nota: aquí, el término "Pasos" simplemente significa las diferentes etapas del formulario de varios pasos)
A continuación, hagamos el primer paso de nuestro formulario: un mensaje introductorio simple.
Crear un nuevo archivo en ./components/First/index.js
:
import { Box, Text, TextInput } from '@mantine/core';
function First() {
return (
<Box
sx={boxStyle}
>
<Text>To start with, whats your beautiful name?</Text>
<Box
sx={{
margin: '1rem 0',
}}
>
<TextInput
placeholder="John Doe"
required
/>
</Box>
</Box>
);
}
export default First;
Así es como debería verse su salida ahora:
useState
Hook para moverse entre los componentesA continuación, usaremos el useState
Hook para movernos entre los diferentes pasos.
Solo hemos hecho un paso hasta ahora, pero siéntase libre de agregar sus propios pasos. A continuación se muestra cómo debería verse el flujo en este tutorial.
Ahora, con los componentes listos, agreguemos la lógica para cambiar entre componentes.
Inicializa un nuevo useState
Hook, con su valor inicial como 0.
const [page, setPage] = useState(0);
Cree otra función que renderice condicionalmente una base de componente diferente en el page
valor:
import First from './First';
import SecondStep from './Second';
import ThirdStep from './Third';
export default function Form () {
const conditionalComponent = () => {
switch (page) {
case 0:
return <First />;
case 1:
return <SecondStep />;
case 2:
return <ThirdStep />;
default:
return <First />;
}
};
return (
<>
<Box>
{conditionalComponent()}
</Box>
</>
)
}
A continuación, creemos un botón que, en onClick
, incremente el valor de page
en 1:
//...imports
import { Button } from "@mantine/core"
export default function Form () {
// stuff
function handleSubmit () {
//...stuff
}
return (
<>
{conditionalComponent()}
<Button onClick={handleSubmit}>
{ page === 0 || page === 1 ? "Next" : "Submit" }
</Button>
</>
)
}
Mientras tanto, en la handleSubmit
función, incrementaremos la página. Más adelante en este artículo, agregaremos validaciones y usaremos la fetch
API para enviar datos a una instancia de Supabase.
function handleSubmit () {
setPage(page + 1);
}
Del mismo modo, necesitamos un botón y una función para volver al anterior page
. El botón Atrás no se mostrará si el usuario está en la primera página, por lo tanto:
{
page > 0 && <Button onClick={() => setPage(page - 1)}>Back</Button>
}
Para realizar un seguimiento de la entrada del usuario, crearemos otro useState
gancho.
const [formData, setFormData] = useState({
name: '',
email: '',
employment_status: null
});
A continuación, pasaremos tanto formData
como setFormData
accesorios a cada uno de nuestros componentes condicionales.
const conditionalComponent = () => {
switch (page) {
case 0:
return <First formData={formData} setFormData={setFormData} />;
case 1:
return <SecondStep formData={formData} setFormData={setFormData} />;
case 2:
return <ThirdStep formData={formData} setFormData={setFormData} />;
default:
return <First formData={formData} setFormData={setFormData} />;
}
};
Para obtener los datos del formulario de nuestros campos de entrada, haremos uso de la propiedad value
y onChange
dentro de los Input
campos.
import { Box, Text, TextInput } from '@mantine/core';
function First({ formData, setFormData }) {
return (
<Box
sx={boxStyle}
>
<Text>To start with, whats your beautiful name?</Text>
<Box>
<TextInput
onChange={(e) => {
setFormData({
...formData,
name: e.target.value,
});
}}
value={formData.name}
placeholder="John Doe"
required
/>
</Box>
</Box>
);
}
export default First;
Dos cosas están sucediendo aquí:
value
respectivoInput
onChange
propiedad se llama cada vez que el usuario intenta cambiar el Input
campoEl select
componente se convierte en una entrada controlada de la siguiente manera:
<Select
data={[
{ value: 'Student', label: 'Student' },
{ value: 'Employed', label: 'Employed' },
{ value: 'Business', label: 'Business' },
]}
onChange={(e) => {
console.log(e);
setFormData({ ...formData, employment_status: e });
}}
value={formData.employment_status}
sx={selectInputStyle}
label="Your emplyment status"
placeholder="Choose one"
/>
Aquí, en lugar de e.target.value
, simplemente estamos pasando el e
objeto como un todo. Asegúrese de no pasar nada más que una cadena en el data
accesorio dentro del Select
componente.
En nuestro último paso, simplemente mostraremos los datos del formulario para que el usuario los verifique y verifique y, si es necesario, le daremos la opción al usuario de regresar y cambiar la entrada ingresada anteriormente.
Dado que no tenemos ningún campo de entrada en el último componente, simplemente pase formData
y muéstrelos en consecuencia:
import { Box, Text } from '@mantine/core';
import Detail from '../Detail';
function ThirdStep({ formData }) {
const boxStyle = {
width: '70%',
margin: '1rem auto',
textAlign: 'center',
padding: '1rem 0',
};
return (
<Box sx={{ ...boxStyle, textAlign: 'left' }}>
<Detail title="Name" data={formData.name} />
<Detail title="Email" data={formData.email} />
<Detail title="Employment status" data={formData.employment_status} />
</Box>
);
}
export default ThirdStep;
El Detail
componente se ve así:
import { Box, Text } from '@mantine/core';
function Detail({ title, data }) {
return (
<Box
sx={{
margin: '10px 0',
}}
>
<Text weight={300}>{title}</Text>
<Text>{data}</Text>
</Box>
);
}
export default Detail;
Una vez hecho esto, su formulario de varios pasos ahora puede realizar un seguimiento de los datos del formulario sin problemas.
A continuación, agregaremos la validación de formularios para aceptar datos válidos del usuario.
En la handleSubmit
función, crearemos un if...else
flujo para verificar los campos de entrada en cada página y luego mover al usuario a la página siguiente.
function handleSubmit () {
if (page === 0) {
// do form validation
} else if (page === 1) {
// do form validation again
} else if (page === 2) {
// set page === 0 , and clear fields
} else setPage(page + 1);
}
En la primera if
declaración:
if (page === 0) {
if (formData.name === '' || formData.name.length <= 1) {
return alert('Please enter your name');
} else {
setPage(page + 1);
console.log(formData);
}
}
Aquí, si el name
campo está vacío o si la longitud es menor que 1, alert
se muestra un cuadro.
De manera similar, agregamos la siguiente validación a la segunda página, que consta de un campo de dirección de correo electrónico y un campo para employment_status
:
else if (page === 1) {
if (formData.email === '' || !formData.email.includes('@')) {
return alert('Please enter a valid email');
} else if (!formData.employment_status) {
return alert('Please select your employment status');
} else {
setPage(page + 1);
console.log(formData);
}
}
Aquí, primero verificaremos si el correo electrónico existe o si el correo electrónico contiene el símbolo "@". Si pasa la primera verificación, verificaremos si el employment_status
campo contiene algo; de lo contrario, incrementamos page
y registramos los datos.
Para la última página, simplemente queremos mostrar los datos del formulario, por lo tanto, es suficiente establecer page
en 0 y borrar todos los campos de entrada.
else if (page === 2) {
setPage(0);
console.log(formData);
setFormData({
name: '',
email: '',
employment_status: null,
});
}
¡Eso se encarga de toda nuestra validación!
Enviemos los datos de nuestro formulario a una nueva instancia de Supabase. Supabase es una alternativa de código abierto a Firebase. Dirígete a Supabase para crear una nueva cuenta si eres nuevo.
Después de crear el proyecto, diríjase a la página de inicio de su proyecto recién creado y tome el anon key
y url
.
(Nota: si no puede encontrar sus claves, intente encontrarlas en Configuración > API )
Con eso completo, ahora instale Supabase SDK, usando:
npm install @supabase/supabase-js
Cree un nuevo archivo ./lib/supabase.js
y exporte el cliente de Supabase, como se muestra a continuación.
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_KEY
)
Para las variables de entorno, cree un nuevo archivo en la raíz con el nombre .env.local
y pase las claves.
NEXT_PUBLIC_SUPABASE_KEY=<Your key>
NEXT_PUBLIC_SUPABASE_URL=<Your url>
Vuelva a la instancia de Supabase recién creada y cree una nueva tabla en el editor de tablas.
Para manejar la lógica de Supabase, crearemos una ruta de API Next.js separada, por lo tanto, realizaremos una POST
solicitud a través de la API Fetch nativa.
Continúe y cree una nueva ruta API Next.js en ./pages/api/post-supabase.js
.
async function handler (req, res) {
// server-side logic
}
export default handler;
Ahora, dado que tenemos una ruta API a través de la cual pasamos los datos de nuestro formulario, podemos agregar otra capa de protección para validar los datos de nuestro formulario. La validación del lado del cliente no siempre es confiable, por lo que siempre debe implementar la validación del lado del servidor antes de guardar los datos del usuario en la tabla de Supabase.
Dentro de la handler
función, primero verificaremos si el método de búsqueda es POST
.
async function handler (req, res) {
if(req.method === 'POST') {
// post logic
return res.status(200).json({message: "Success"});
}
}
A continuación, desestructura todos los datos del formulario del cuerpo de la solicitud, como se muestra aquí:
if(req.method === "POST") {
const { name, email, employment_status } = req.body;
}
A continuación, realizaremos la validación del lado del servidor:
if(req.method === "POST") {
const { name, email, employment_status } = req.body;
// server-side validation
if (!name || name.length <= 3) {
return res.status(400).json({
status: 'fail',
message: 'Please enter your name',
});
} else if (!email || !email.includes('@')) {
return res.status(400).json({
status: 'fail',
message: 'Please enter a valid email',
});
} else if (!employment_status) {
return res.status(400).json({
status: 'fail',
message: 'Please select your employment status',
});
}
}
En este caso, si alguien intenta alterar la validación del lado del cliente o realizar un ataque MITM, el servidor aún podrá negar los datos del formulario y devolver una respuesta de error.
Con la validación completa, todo lo que queda es enviar los datos del formulario a la tabla de Supabase. Para hacer esto, importe el cliente de Supabase y llame al insert
método.
import { supabase } from 'utils/supabase';
async function handler (req, res) {
// ...server-side validation
try {
await supabase.from('formdata').insert({
name,
email,
employment_status,
});
return res
.status(200)
.json({ success: true, message: 'Post successful' });
} catch (error) {
console.log(error.response.data.message);
return res.status(500).json({
status: 'fail',
message: 'Something went wrong',
});
}
}
.from
es la tabla en la que desea insertar los datos. Ahora que esto está hecho, haremos una POST
solicitud a esta ruta API desde el lado del cliente cuando el usuario haga clic en el botón para enviar.
Regrese a la handleSubmit
función, donde escribimos la lógica de envío para page === 2
. Llamemos una solicitud de búsqueda a la ruta API /api/post-supabase
:
try {
await fetch('/api/post-supabase', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
setFormData({
name: '',
email: '',
mployment_status: null,
});
setPage(0);
} catch (error) {
console.log(error);
}
Si el fetch
método se ejecuta como se esperaba, restableceremos los datos del formulario y los estableceremos page
en 0, y si algo sale mal, simplemente registraremos el error (también puede mostrar esto como una alerta).
Continúe y pruebe el formulario de varios pasos: debería poder ver una nueva entrada en la tabla de Supabase.
En esta publicación de blog, hemos aprendido cómo crear un formulario de varios pasos. Usando el useState
Hook, hemos logrado crear formularios que pueden manipularse fácilmente.
Podemos crear formularios que tengan validación y puedan ser fácilmente reutilizados sin necesidad de crear nuevos formularios para diferentes casos de uso. Lo más importante es que hemos creado este formulario de varios pasos sin ningún paquete de terceros como React-Stepzilla.
Fuente: https://blog.logrocket.com/build-multi-step-form-usestate-hook/
1651705500
フォームは、ほとんどのアプリケーションの重要な部分です。フォームを作成するためのさまざまなパッケージで利用できるパッケージがたくさんあります。useState
この記事では、同じ機能を実現しながら、サードパーティのパッケージを使用せずにフックを使用してマルチステップフォームを作成します。
チュートリアルの主な優先事項は、マルチステップフォームのロジックを分析することです。スタイリング部分のほとんどをスキップするので、どこかで行き詰まった場合は、このリポジトリをチェックしてください。
次のコマンドを入力して、新しいNext.jsプロジェクトをインストールしましょう。
npx create-next-app mulitstep-form
必要に応じて、Reactプロジェクトも完全に機能します。新しいReactプロジェクトをインストールするには、次のように入力します。
npx create-react-app multistep-form
スタイリングはオプションですが、フォローしたい場合は、このプロジェクトにMantineCSSを使用します。
MantineCSSをインストールする
npm install @mantine/core @mantine/next @mantine/hooks
こちらのNext.jsのセットアップガイドに従ってください。
Next, let’s build some components for each step in our multi-step form.
まず、Form
フォームロジックのほとんどを処理するコンポーネントを作成します。先に進み、で新しいファイルを作成します./components/Form.js
。
import { Box, Button, Title } from '@mantine/core';
function Form() {
return (
<Box
sx={boxStyle}
>
<Title
sx={{
textAlign: 'center',
}}
order={2}
>
Hey there!
</Title>
{/* Steps */}
<Button>Submit</Button>
</Box>
);
}
export default Form;
(注:ここで、「ステップ」という用語は、単にマルチステップフォームのさまざまなステージを意味します)
次に、フォームの最初のステップである簡単な紹介メッセージを作成しましょう。
で新しいファイルを作成します./components/First/index.js
:
import { Box, Text, TextInput } from '@mantine/core';
function First() {
return (
<Box
sx={boxStyle}
>
<Text>To start with, whats your beautiful name?</Text>
<Box
sx={{
margin: '1rem 0',
}}
>
<TextInput
placeholder="John Doe"
required
/>
</Box>
</Box>
);
}
export default First;
これはあなたの出力が今どのように見えるべきかです:
useState
次に、useState
フックを使用してさまざまなステップ間を移動します。
これまでは1つのステップしか実行していませんが、独自のステップを自由に追加してください。このチュートリアルでのフローは次のようになります。
コンポーネントの準備ができたら、コンポーネントを切り替えるロジックを追加しましょう。
初期値を0にして、新しいuseState
フックを初期化します。
const [page, setPage] = useState(0);
page
値に基づいて異なるコンポーネントを条件付きでレンダリングする別の関数を作成します。
import First from './First';
import SecondStep from './Second';
import ThirdStep from './Third';
export default function Form () {
const conditionalComponent = () => {
switch (page) {
case 0:
return <First />;
case 1:
return <SecondStep />;
case 2:
return <ThirdStep />;
default:
return <First />;
}
};
return (
<>
<Box>
{conditionalComponent()}
</Box>
</>
)
}
onClick
次に、の値をpage
1ずつ増やすボタンを作成しましょう。
//...imports
import { Button } from "@mantine/core"
export default function Form () {
// stuff
function handleSubmit () {
//...stuff
}
return (
<>
{conditionalComponent()}
<Button onClick={handleSubmit}>
{ page === 0 || page === 1 ? "Next" : "Submit" }
</Button>
</>
)
}
その間、handleSubmit
関数では、ページをインクリメントします。この記事のさらに先では、検証を追加し、fetch
APIを使用してデータをSupabaseインスタンスに送信します。
function handleSubmit () {
setPage(page + 1);
}
同様に、前に戻るためのボタンと関数が必要page
です。ユーザーが最初のページを表示している場合、戻るボタンは表示されません。したがって、次のようになります。
{
page > 0 && <Button onClick={() => setPage(page - 1)}>Back</Button>
}
ユーザー入力を追跡するために、別のuseState
フックを作成します。
const [formData, setFormData] = useState({
name: '',
email: '',
employment_status: null
});
次に、両方formData
とsetFormData
小道具として各条件付きコンポーネントに渡します。
const conditionalComponent = () => {
switch (page) {
case 0:
return <First formData={formData} setFormData={setFormData} />;
case 1:
return <SecondStep formData={formData} setFormData={setFormData} />;
case 2:
return <ThirdStep formData={formData} setFormData={setFormData} />;
default:
return <First formData={formData} setFormData={setFormData} />;
}
};
入力フィールドからフォームデータを取得するには、フィールド内のvalue
andonChange
プロパティを使用しInput
ます。
import { Box, Text, TextInput } from '@mantine/core';
function First({ formData, setFormData }) {
return (
<Box
sx={boxStyle}
>
<Text>To start with, whats your beautiful name?</Text>
<Box>
<TextInput
onChange={(e) => {
setFormData({
...formData,
name: e.target.value,
});
}}
value={formData.name}
placeholder="John Doe"
required
/>
</Box>
</Box>
);
}
export default First;
ここでは2つのことが起こっています。
value
プロパティには、それぞれのInput
フィールドの現在の値が含まれますonChange
を変更しようとするたびに呼び出されますInput
コンポーネントは、select
次のように制御された入力になります。
<Select
data={[
{ value: 'Student', label: 'Student' },
{ value: 'Employed', label: 'Employed' },
{ value: 'Business', label: 'Business' },
]}
onChange={(e) => {
console.log(e);
setFormData({ ...formData, employment_status: e });
}}
value={formData.employment_status}
sx={selectInputStyle}
label="Your emplyment status"
placeholder="Choose one"
/>
ここでは、の代わりに、オブジェクト全体e.target.value
を渡すだけです。コンポーネント内の支柱にe
文字列以外のものを渡さないように注意してください。dataSelect
最後のステップでは、ユーザーがクロスチェックして確認できるようにフォームデータを表示し、必要に応じて、前に入力した入力に戻って変更するオプションをユーザーに提供します。
最後のコンポーネントには入力フィールドがないため、を渡してformData
それに応じて表示します。
import { Box, Text } from '@mantine/core';
import Detail from '../Detail';
function ThirdStep({ formData }) {
const boxStyle = {
width: '70%',
margin: '1rem auto',
textAlign: 'center',
padding: '1rem 0',
};
return (
<Box sx={{ ...boxStyle, textAlign: 'left' }}>
<Detail title="Name" data={formData.name} />
<Detail title="Email" data={formData.email} />
<Detail title="Employment status" data={formData.employment_status} />
</Box>
);
}
export default ThirdStep;
コンポーネントは次のDetail
ようになります。
import { Box, Text } from '@mantine/core';
function Detail({ title, data }) {
return (
<Box
sx={{
margin: '10px 0',
}}
>
<Text weight={300}>{title}</Text>
<Text>{data}</Text>
</Box>
);
}
export default Detail;
これで、マルチステップフォームでフォームデータをシームレスに追跡できるようになります。
次に、フォーム検証を追加して、ユーザーからの有効なデータを受け入れます。
この関数では、各ページの入力フィールドを確認し、その後ユーザーを次のページに移動するフローをhandleSubmit
作成します。if...else
function handleSubmit () {
if (page === 0) {
// do form validation
} else if (page === 1) {
// do form validation again
} else if (page === 2) {
// set page === 0 , and clear fields
} else setPage(page + 1);
}
最初のif
ステートメントで:
if (page === 0) {
if (formData.name === '' || formData.name.length <= 1) {
return alert('Please enter your name');
} else {
setPage(page + 1);
console.log(formData);
}
}
ここで、name
フィールドが空の場合、または長さが1未満の場合、alert
ボックスが表示されます。
同様に、次の検証を2番目のページに追加します。これは、電子メールアドレスフィールドと次のフィールドで構成されていますemployment_status
。
else if (page === 1) {
if (formData.email === '' || !formData.email.includes('@')) {
return alert('Please enter a valid email');
} else if (!formData.employment_status) {
return alert('Please select your employment status');
} else {
setPage(page + 1);
console.log(formData);
}
}
ここでは、最初に電子メールが存在するかどうか、または電子メールに「@」記号が含まれているかどうかを確認します。最初のチェックに合格すると、employment_status
フィールドに何かが含まれているかどうかをチェックします。それ以外の場合は、データをインクリメントpage
してログに記録します。
最後のページでは、フォームデータを表示するだけなので、page
を0に設定して、すべての入力フィールドをクリアするだけで十分です。
else if (page === 2) {
setPage(0);
console.log(formData);
setFormData({
name: '',
email: '',
employment_status: null,
});
}
これですべての検証が完了します。
フォームデータを新しいSupabaseインスタンスに送信しましょう。Supabaseは、Firebaseに代わるオープンソースです。初めての場合は、 Supabaseにアクセスして新しいアカウントを作成してください。
プロジェクトを作成したら、新しく作成したプロジェクトのホームページに移動して、とを取得しanon key
ますurl
。
(注:キーが見つからない場合は、 [設定] > [ API ]でキーを見つけてみてください)
これが完了したら、次を使用してSupabaseSDKをインストールします。
npm install @supabase/supabase-js
以下に示すように、で新しいファイルを作成し./lib/supabase.js
、Supabaseクライアントをエクスポートします。
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_KEY
)
環境変数については、ルートに名前を付けて新しいファイルを作成.env.local
し、キーを渡します。
NEXT_PUBLIC_SUPABASE_KEY=<Your key>
NEXT_PUBLIC_SUPABASE_URL=<Your url>
新しく作成されたSupabaseインスタンスに戻り、テーブルエディタで新しいテーブルを作成します。
Supabaseロジックを処理するために、別のNext.js APIルートを作成しPOST
、ネイティブのFetchAPIを介してリクエストを行います。
先に進み、で新しいNext.jsAPIルートを作成し./pages/api/post-supabase.js
ます。
async function handler (req, res) {
// server-side logic
}
export default handler;
これで、フォームデータを渡すAPIルートがあるので、フォームデータを検証するための保護の別のレイヤーを追加できます。クライアント側の検証は常に信頼できるとは限らないため、ユーザーデータをSupabaseテーブルに保存する前に、常にサーバー側の検証も実装する必要があります。
関数内ではhandler
、最初に、fetchメソッドがであるかどうかを確認しPOST
ます。
async function handler (req, res) {
if(req.method === 'POST') {
// post logic
return res.status(200).json({message: "Success"});
}
}
次に、次に示すように、リクエスト本文からすべてのフォームデータを分解します。
if(req.method === "POST") {
const { name, email, employment_status } = req.body;
}
次に、サーバー側の検証を実行します。
if(req.method === "POST") {
const { name, email, employment_status } = req.body;
// server-side validation
if (!name || name.length <= 3) {
return res.status(400).json({
status: 'fail',
message: 'Please enter your name',
});
} else if (!email || !email.includes('@')) {
return res.status(400).json({
status: 'fail',
message: 'Please enter a valid email',
});
} else if (!employment_status) {
return res.status(400).json({
status: 'fail',
message: 'Please select your employment status',
});
}
}
この場合、誰かがクライアント側の検証を改ざんしたり、MITM攻撃を実行したりしようとしても、サーバーはフォームデータを拒否し、エラー応答を返すことができます。
検証が完了すると、あとはフォームデータをSupabaseテーブルに送信するだけです。これを行うには、Supabaseクライアントをインポートし、insert
メソッドを呼び出します。
import { supabase } from 'utils/supabase';
async function handler (req, res) {
// ...server-side validation
try {
await supabase.from('formdata').insert({
name,
email,
employment_status,
});
return res
.status(200)
.json({ success: true, message: 'Post successful' });
} catch (error) {
console.log(error.response.data.message);
return res.status(500).json({
status: 'fail',
message: 'Something went wrong',
});
}
}
.from
データを挿入するテーブルです。これでPOST
、ユーザーがボタンをクリックして送信したときに、クライアント側からこのAPIルートにリクエストを送信します。
handleSubmit
の送信ロジックを記述した関数に戻りpage === 2
ます。APIルートへのフェッチリクエストを呼び出しましょう/api/post-supabase
:
try {
await fetch('/api/post-supabase', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
setFormData({
name: '',
email: '',
mployment_status: null,
});
setPage(0);
} catch (error) {
console.log(error);
}
メソッドが期待どおりに実行された場合はfetch
、フォームデータをリセットしpage
て0に設定し、問題が発生した場合は、エラーをログに記録します(これをアラートとして表示することもできます)。
先に進んで、マルチステップフォームを試してください。Supabaseテーブルに新しいエントリが表示されるはずです。
このブログ投稿では、マルチステップフォームを作成する方法を学びました。フックを使用して、useState
簡単に操作できるフォームを作成することができました。
検証があり、さまざまなユースケースの新しいフォームを作成しなくても簡単に再利用できるフォームを作成できます。最も重要なことは、React-Stepzillaのようなサードパーティのパッケージを使用せずにこのマルチステップフォームを構築したことです。
ソース:https ://blog.logrocket.com/build-multi-step-form-usestate-hook/
1651249939
Run this command:
With Flutter:
$ flutter pub add flutter_use_geolocation
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
flutter_use_geolocation: ^0.0.2
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:flutter_use_geolocation/flutter_use_geolocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_use_geolocation/use_geolocation.dart';
import 'package:geolocator/geolocator.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class SampleError extends Error {
SampleError(this.message);
final String message;
}
class UseError extends Error {}
class SampleException implements Exception {}
class UseException implements Exception {}
class MyHomePage extends HookWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint("build");
final geolocation = useGeolocation();
return Scaffold(
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 32),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text("-- Geolocation --"),
Text("permission checked: ${geolocation.fetched}"),
Text("location: ${geolocation.position}"),
ElevatedButton(
onPressed: () async {
await Geolocator.requestPermission();
},
child: const Text('Grant Location permission'),
),
],
),
),
),
);
}
}
Download Details:
Author: wasabeef
Source Code: https://github.com/wasabeef/flutter_use
1649499000
Release It! 🚀
🚀 Generic CLI tool to automate versioning and package publishing related tasks:
package.json
)Use release-it for version management and publish to anywhere with its versatile configuration, a powerful plugin system, and hooks to execute any command you need to test, build, and/or publish your project.
Although release-it is a generic release tool, installation requires npm. To use release-it, a package.json
file is not required. The recommended way to install release-it also adds basic configuration. Answer one or two questions and it's ready:
npm init release-it
Alternatively, install it manually, and add the release
script to package.json
:
npm install --save-dev release-it
{
"name": "my-package",
"version": "1.0.0",
"scripts": {
"release": "release-it"
},
"devDependencies": {
"release-it": "*"
}
}
Now you can run npm run release
from the command line (any release-it arguments behind the --
):
npm run release
npm run release -- minor --ci
Use release-it in any (non-npm) project, take it for a test drive, or install it globally:
# Run release-it from anywhere (without installation)
npx release-it
# Install globally and run from anywhere
npm install --global release-it
release-it
Release a new version:
release-it
You will be prompted to select the new version, and more prompts will follow based on your setup.
Run release-it from the root of the project to prevent potential issues.
Use --dry-run
to show the interactivity and the commands it would execute.
→ See Dry Runs for more details.
To print the next version without releasing anything, add the --release-version
flag.
Out of the box, release-it has sane defaults, and plenty of options to configure it. Most projects use a .release-it.json
in the project root, or a release-it
property in package.json
.
→ See Configuration for more details.
Here's a quick example .release-it.json
:
{
"git": {
"commitMessage": "chore: release v${version}"
},
"github": {
"release": true
}
}
By default, release-it is interactive and allows you to confirm each task before execution:
By using the --ci
option, the process is fully automated without prompts. The configured tasks will be executed as demonstrated in the first animation above. On a Continuous Integration (CI) environment, this non-interactive mode is activated automatically.
Use --only-version
to use a prompt only to determine the version, and automate the rest.
How does release-it determine the latest version?
package.json
, its version
will be used (see npm to skip this).0.0.0
will be used as the latest version.Alternatively, a plugin can be used to override this (e.g. to manage a VERSION
or composer.json
file):
Add the --release-version
flag to print the next version without releasing anything.
Git projects are supported well by release-it, automating the tasks to stage, commit, tag and push releases to any Git remote.
→ See Git for more details.
GitHub projects can have releases attached to Git tags, containing release notes and assets. There are two ways to add GitHub releases in your release-it flow:
GITHUB_TOKEN
)→ See GitHub Releases for more details.
GitLab projects can have releases attached to Git tags, containing release notes and assets. To automate GitLab releases:
gitlab.release: true
→ See GitLab Releases for more details.
By default, release-it generates a changelog, to show and help select a version for the new release. Additionally, this changelog serves as the release notes for the GitHub or GitLab release.
The default command is based on git log ...
. This setting (git.changelog
) can be overridden. To further customize the release notes for the GitHub or GitLab release, there's github.releaseNotes
or gitlab.releaseNotes
. Make sure any of these commands output the changelog to stdout
. Plugins are available for:
→ See Changelog for more details.
With a package.json
in the current directory, release-it will let npm
bump the version in package.json
(and package-lock.json
if present), and publish to the npm registry.
→ See Publish to npm for more details.
With release-it, it's easy to create pre-releases: a version of your software that you want to make available, while it's not in the stable semver range yet. Often "alpha", "beta", and "rc" (release candidate) are used as identifier for pre-releases. An example pre-release version is 2.0.0-beta.0
.
→ See Manage pre-releases for more details.
Use --no-increment
to not increment the last version, but update the last existing tag/version.
This may be helpful in cases where the version was already incremented. Here's a few example scenarios:
release-it --no-increment --no-npm
to skip the npm publish
and try pushing the same Git tag again.Use script hooks to run shell commands at any moment during the release process (such as before:init
or after:release
).
The format is [prefix]:[hook]
or [prefix]:[plugin]:[hook]
:
part | value |
---|---|
prefix | before or after |
plugin | version , git , npm , github , gitlab |
hook | init , bump , release |
Use the optional :plugin
part in the middle to hook into a life cycle method exactly before or after any plugin.
The core plugins include version
, git
, npm
, github
, gitlab
.
Note that hooks like after:git:release
will not run when either the git push
failed, or when it is configured not to be executed (e.g. git.push: false
). See execution order for more details on execution order of plugin lifecycle methods.
All commands can use configuration variables (like template strings). An array of commands can also be provided, they will run one after another. Some example release-it configuration:
{
"hooks": {
"before:init": ["npm run lint", "npm test"],
"after:my-plugin:bump": "./bin/my-script.sh",
"after:bump": "npm run build",
"after:git:release": "echo After git push, before github release",
"after:release": "echo Successfully released ${name} v${version} to ${repo.repository}."
}
}
The variables can be found in the default configuration. Additionally, the following variables are exposed:
version
latestVersion
changelog
name
repo.remote, repo.protocol, repo.host, repo.owner, repo.repository, repo.project
All variables are available in all hooks. The only exception is that the additional variables listed above are not yet available in the init
hook.
Use --verbose
to log the output of the commands.
For the sake of verbosity, the full list of hooks is actually: init
, beforeBump
, bump
, beforeRelease
, release
or afterRelease
. However, hooks like before:beforeRelease
look weird and are usually not useful in practice.
Since v11, release-it can be extended in many, many ways. Here are some plugins:
Plugin | Description |
---|---|
@release-it/bumper | Read & write the version from/to any file |
@release-it/conventional-changelog | Provides recommended bump, conventional-changelog, and updates CHANGELOG.md |
@release-it/keep-a-changelog | Maintain CHANGELOG.md using the Keep a Changelog standards |
release-it-lerna-changelog | Integrates lerna-changelog into the release-it pipeline |
release-it-yarn-workspaces | Releases each of your projects configured workspaces |
release-it-calver-plugin | Enables Calendar Versioning (calver) with release-it |
@grupoboticario/news-fragments | An easy way to generate your changelog file |
@j-ulrich/release-it-regex-bumper | Regular expression based version read/write plugin for release-it |
Internally, release-it uses its own plugin architecture (for Git, GitHub, GitLab, npm).
→ See all release-it plugins on npm.
→ See plugins for documentation to write plugins.
Deprecated. Please see distribution repository for more details.
Use --disable-metrics
to opt-out of sending some anonymous statistical data to Google Analytics. For details, refer to lib/metrics.js. Please consider to not opt-out: more data means more support for future development.
release-it --verbose
(or -V
), release-it prints the output of every user-defined hook.release-it -VV
, release-it also prints the output of every internal command.DEBUG=release-it:* release-it [...]
to print configuration and more error details.Use verbose: 2
in a configuration file to have the equivalent of -VV
on the command line.
While mostly used as a CLI tool, release-it can be used as a dependency to integrate in your own scripts. See use release-it programmatically for example code.
Author: Release-it
Source Code: https://github.com/release-it/release-it
License: MIT License
1649442360
quickhook
Quickhook is a fast, Unix'y, opinionated Git hook runner. It handles running all user-defined hooks, collecting their output, reporting failures, and exiting with a non-zero status code if appropriate.
If you're on Mac there is a Homebrew tap for Quickhook:
brew tap dirk/quickhook
# Tapped 1 formula (28 files, 28KB).
brew install quickhook
# /usr/local/Cellar/quickhook/1.4.0: 3 files, 3.7MB, built in 9 seconds
First you'll need to set Quickhook to be called in your Git hooks. The quickhook install
command will discover hooks defined in the .quickhook
directory and create Git hook shims for those. For example, the below is what you can expect if you clone this repository and install the shims:
$ quickhook install
Create file .git/hooks/commit-msg? [yn] y
Installed shim .git/hooks/commit-msg
Create file .git/hooks/pre-commit? [yn] y
Installed shim .git/hooks/pre-commit
To make it easier to test hooks outside of the normal Git paths the hook
sub-commands provide some additional options. For example, these are some of the options you can use with the pre-commit hook command:
# Run the pre-commit hooks on all Git-tracked files in the repository
quickhook hook pre-commit --all
# Run them on just one or more files
quickhook hook pre-commit --files hooks/commit_msg.go hooks/pre_commit.go
You can see all of the options by passing --help
to the sub-command:
$ quickhook hook pre-commit --help
...
OPTIONS:
--all, -a Run on all Git-tracked files
--files, -F Run on the given comma-separated list of files
Quickhook will look for hooks in a corresponding sub-directory of the .quickhook
directory in your repository. For example, it will look for pre-commit hooks in .quickhook/pre-commit/
. A hook is any executable file in that directory. See the go-vet
file for an example.
Pre-commit hooks receive the list of staged files separated by newlines on stdin. They are expected to write their result to stdout/stderr (Quickhook doesn't care). If they exit with a non-zero exit code then the commit will be aborted and their output displayed to the user.
Note: Pre-commit hooks will be executed in parallel and should not mutate the local repository state.
File-and-line-specific errors should be written in the following format:
some/directory/and/file.go:123: Something doesn't look right
A more formal definition of an error line is:
:
) character\n
) terminating the error lineThis informal Unix convention is already followed by many programming languages, linters, and so forth.
Commit-message hooks are run sequentially. They receive a single argument: a path to a temporary file containing the message for the commit. If they exit with a non-zero exit code the commit will be aborted and any stdout/stderr output displayed to the user.
Given that they are run sequentially, commit-msg
hooks are allowed to mutate the commit message temporary file.
Quickhook is designed to be as fast and lightweight as possible. There are a few guiding principles for this:
Author: Dirk
Source Code: https://github.com/dirk/quickhook/
License: BSD-3-Clause License
1649434943
overcommit
is a tool to manage and configure Git hooks.
In addition to supporting a wide variety of hooks that can be used across multiple repositories, you can also define hooks specific to a repository which are stored in source control. You can also easily add your existing hook scripts without writing any Ruby code.
This project aims to support the following Ruby runtimes on both *nix and Windows:
If you are using Overcommit on Windows, make sure you include the ffi
gem in your list of dependencies. Overcommit does not include the ffi
gem by default since it significantly increases the install time for non-Windows platforms.
Some hooks have third-party dependencies. For example, to lint your SCSS files, you're going to need the scss_lint gem.
Depending on the hooks you enable/disable for your repository, you'll need to ensure your development environment already has those dependencies installed. Most hooks will display a warning if a required executable isn't available.
If you are using Bundler to manage your Ruby gem dependencies, you'll likely want to use the gemfile
option to control which gem versions are available during your hook runs.
overcommit
is installed via RubyGems. It is strongly recommended that your environment support running gem install
without requiring root user privileges via sudo
or otherwise. Using a Ruby version manager like rbenv
or rvm
is recommended.
Once you have an environment that allows you to install gems without sudo
, run:
gem install overcommit
You can then run the overcommit
command to install hooks into repositories.
mkdir important-project
cd important-project
git init
overcommit --install
After running overcommit --install
, any existing hooks for your repository which Overcommit will replace will be backed up. You can restore everything to the way it was by running overcommit --uninstall
.
If you want to use overcommit
for all repositories you create/clone going forward, add the following to automatically run in your shell environment:
export GIT_TEMPLATE_DIR="$(overcommit --template-dir)"
The GIT_TEMPLATE_DIR
provides a directory for Git to use as a template for automatically populating the .git
directory. If you have your own template directory, you might just want to copy the contents of overcommit --template-dir
to that directory.
Once you've installed the hooks via overcommit --install
, they will automatically run when the appropriate hook is triggered.
The overcommit
executable supports the following command-line flags:
Command Line Flag | Description |
---|---|
-i /--install | Install Overcommit hooks in a repository |
-u /--uninstall | Remove Overcommit hooks from a repository |
-f /--force | Don't bail on install if other hooks already exist--overwrite them |
-l /--list-hooks | Display all available hooks in the current repository |
-r /--run | Run pre-commit hook against all tracked files in repository |
-t /--template-dir | Print location of template directory |
-h /--help | Show command-line flag documentation |
-v /--version | Show version |
Sometimes a hook will report an error that for one reason or another you'll want to ignore. To prevent these errors from blocking your commit, you can include the name of the relevant hook in the SKIP
environment variable, e.g.
SKIP=RuboCop git commit
If you would prefer to specify a whitelist of hooks rather than a blacklist, use the ONLY
environment variable instead.
ONLY=RuboCop git commit
Use this feature sparingly, as there is no point to having the hook in the first place if you're just going to ignore it. If you want to ensure a hook is never skipped, set the required
option to true
in its configuration. If you attempt to skip it, you'll see a warning telling you that the hook is required, and the hook will still run.
If you have scripts that execute git
commands where you don't want Overcommit hooks to run, you can disable Overcommit entirely by setting the OVERCOMMIT_DISABLE
environment variable.
OVERCOMMIT_DISABLE=1 ./my-custom-script
Overcommit automatically colorizes its output based on whether it is outputting to a TTY. However, you can manually enable/disable color by setting the OVERCOMMIT_COLOR
environment variable.
OVERCOMMIT_COLOR=0 git commit
You can run the same set of hooks that would be executed in a pre-commit hook against your entire repository by running overcommit --run
. This makes it easy to have the checks verified by a CI service such as Travis CI, including custom hooks you've written yourself.
The --run
flag works by creating a pre-commit context that assumes all the files in your repository have changed, and follows the same rules as a normal pre-commit check. If any hook fails with an error, it will return a non-zero exit code.
Overcommit provides a flexible configuration system that allows you to tailor the built-in hooks to suit your workflow. All configuration specific to a repository is stored in .overcommit.yml
in the top-level directory of the repository.
When writing your own configuration, it will automatically extend the default configuration, so you only need to specify your configuration with respect to the default. In order to enable/disable hooks, you can add the following to your repo-specific configuration file:
PreCommit:
RuboCop:
enabled: true
command: ['bundle', 'exec', 'rubocop'] # Invoke within Bundler context
Individual hooks expose both built-in configuration options as well as their own custom options unique to each hook. The following table lists all built-in configuration options:
Option | Description |
---|---|
enabled | If false , this hook will never be run |
required | If true , this hook cannot be skipped via the SKIP environment variable |
quiet | If true , this hook does not display any output unless it warns/fails |
description | Message displayed while hook is running. |
requires_files | If true , this hook runs only if files that are applicable to it have been modified. See include and exclude for how to specify applicable files. |
include | File paths or glob patterns of files that apply to this hook. The hook will only run on the applicable files when they have been modified. Note that the concept of modified varies for different types of hooks. By default, include matches every file until you specify a list of patterns. |
exclude | File paths or glob patterns of files that do not apply to this hook. This is used to exclude any files that would have been matched by include . |
exclude_branches | List of branch names or glob patterns of branches that this hook should not run against. |
exclude_remotes | PrePush hooks only. List of remote names that the hook should not run against. |
include_remote_ref_deletions | PrePush hooks only. By default, PrePush hooks will not run for pushes that delete a remote ref (i.e. branches or tags). Set to true to have the hook run even for deleted remote ref. |
problem_on_unmodified_line | How to treat errors reported on lines that weren't modified during the action captured by this hook (e.g. for pre-commit hooks, warnings/errors reported on lines that were not staged with git add may not be warnings/errors you care about). Valid values are report : report errors/warnings as-is regardless of line location (default); warn : report errors as warnings if they are on lines you didn't modify; and ignore : don't display errors/warnings at all if they are on lines you didn't modify (ignore is not recommended). |
on_fail | Change the status of a failed hook to warn or pass . This allows you to treat failures as warnings or potentially ignore them entirely, but you should use caution when doing so as you might be hiding important information. |
on_warn | Similar to on_fail , change the status of a hook that returns a warning status to either pass (you wish to silence warnings entirely) or fail (you wish to treat all warnings as errors). |
required_executable | Name of an executable that must exist in order for the hook to run. If this is a path (e.g. ./bin/ruby ), ensures that the executable file exists at the given location relative to the repository root. Otherwise, if it just the name of an executable (e.g. ruby ) checks if the executable can be found in one of the directories in the PATH environment variable. Set this to a specific path if you want to always use an executable that is stored in your repository. (e.g. RubyGems bin stubs, Node.js binaries, etc.) |
required_library /required_libraries | List of Ruby libraries to load with Kernel.require before the hook runs. This is specifically for hooks that integrate with external Ruby libraries. |
command | Array of arguments to use as the command. How each hook uses this is different, but it allows hooks to change the context with which they run. For example, you can change the command to be ['bundle', 'exec', 'rubocop'] instead of just rubocop so that you can use the gem versions specified in your local Gemfile.lock . This defaults to the name of the required_executable . |
flags | Array of arguments to append to the command . This is useful for customizing the behavior of a tool. It's also useful when a newer version of a tool removes/renames existing flags, so you can update the flags via your .overcommit.yml instead of waiting for an upstream fix in Overcommit. |
env | Hash of environment variables the hook should be run with. This is intended to be used as a last resort when an executable a hook runs is configured only via an environment variable. Any pre-existing environment variables with the same names as ones defined in env will have their original values restored after the hook runs. NOTE: Currently, only strings are accepted values. Boolean values will raise an error. WARNING: If you set the same environment variable for multiple hooks and you've enabled parallel hook runs, since the environment is shared across all threads you could accidentally have these separate hooks trample on each other. In this case, you should disable parallelization for the hook using the parallelize option. |
parallelize | Whether to allow this hook to be run concurrently with other hooks. Disable this if the hook requires access to a shared resource that other hooks may also access and modify (e.g. files, the git index, process environment variables, etc). |
processors | The number of processing units to reserve for this hook. This does not reserve CPUs, but indicates that out of the total number of possible concurrent hooks allowed by the global concurrency option, this hook requires the specified number. Thus in the typical case where concurrency is set to the number of available cores (default), and you have a hook that executes an application which itself creates 2 threads (or is otherwise scheduled on 2 cores), you can indicate that Overcommit should allocate 2 processors to the hook. Ideally this means your hooks won't put undue load on your available cores. |
install_command | Command the user can run to install the required_executable (or alternately the specified required_libraries ). This is intended for documentation purposes, as Overcommit does not install software on your behalf since there are too many edge cases where such behavior would result in incorrectly configured installations (e.g. installing a Python package in the global package space instead of in a virtual environment). |
skip_file_checkout | Whether to skip this hook for file checkouts (e.g. git checkout some-ref -- file ). Only applicable to PostCheckout hooks. |
skip_if | Array of arguments to be executed to determine whether or not the hook should run. For example, setting this to a value of ['bash', '-c', '! which my-executable'] would allow you to skip running this hook if my-executable was not in the bin path. |
In addition to the built-in configuration options, each hook can expose its own unique configuration options. The AuthorEmail
hook, for example, allows you to customize the regex used to check commit author emails via the pattern
option—useful if you want to enforce that developers use a company email address for their commits. This provides incredible flexibility for hook authors as you can make your hooks sufficiently generic and then customize them on a per-project basis.
Hook configurations are organized into categories based on the type of hook. So pre-commit
hooks are located under the PreCommit
option, and post-commit
hooks are located under PostCommit
. See the default configuration for a thorough example.
ALL
HookWithin a hook category, there is a special type of hook configuration that applies to all hooks in the category. This configuration looks like a normal hook configuration, except it has the name ALL
:
PreCommit:
ALL:
problem_on_unmodified_line: warn
requires_files: true
required: false
quiet: false
SomeHook:
enabled: true
...
The ALL
configuration is useful for when you want to DRY up your configuration, or when you want to apply changes across an entire category of hooks.
Note that array configuration options (like include
/exclude
) in the special ALL
hook section are not merged with individual hook configurations if custom ones are defined for the hook. Any custom configuration option for include
/exclude
will replace the ALL
hook's configuration. If you want to have a global list of default exclusions and extend them with a custom list, you can use YAML references, e.g.
PreCommit:
ALL:
exclude: &default_excludes
- 'node_modules/**/*'
- 'vendor/**/*'
MyHook:
exclude:
- *default_excludes
- 'another/directory/in/addition/to/default/excludes/**/*'
Again, you can consult the default configuration for detailed examples of how the ALL
hook can be used.
You may want to enforce the version of Overcommit or other gems that you use in your git hooks. This can be done by specifying the gemfile
option in your .overcommit.yml
.
The gemfile
option tells Overcommit to load the specified file with Bundler, the standard gem dependency manager for Ruby. This is useful if you would like to:
Loading a Bundler context necessarily adds a startup delay to your hook runs as Bundler parses the specified Gemfile
and checks that the dependencies are satisfied. Thus for projects with many gems this can introduce a noticeable delay.
The recommended workaround is to create a separate Gemfile
in the root of your repository (call it .overcommit_gems.rb
), and include only the gems that your Overcommit hooks need in order to run. Generate the associated lock file by running:
bundle install --gemfile=.overcommit_gems.rb
...and commit .overcommit_gems.rb
and the resulting .overcommit_gems.rb.lock
file to your repository. Set your gemfile
option to .overcommit_gems.rb
, and you're all set.
Using a smaller Gemfile containing only the gems used by your Overcommit hooks significantly reduces the startup delay in your hook runs. It is thus the recommended approach unless your project has a relatively small number of gems in your Gemfile
.
You can change the directory that project-specific hooks are loaded from via the plugin_directory
option. The default directory is .git-hooks
.
If you prefer to have your hooks be completely silent unless there is a problem, you can set the top-level quiet
option to true
. Note that if you have many hooks or slow hooks this may not be desirable, as you don't get visual feedback indicating the general progress of the hook run.
Overcommit runs hooks in parallel by default, with a number of concurrent workers equal to the number of logical cores on your machine. If you know your particular set of hooks would benefit from higher/lower number of workers, you can adjust the global concurrency
option. You can define single-operator mathematical expressions, e.g. %{processors} * 2
, or %{processors} / 2
.
concurrency: '%{processors} / 4'
Note that individual hooks can specify the number of processors they require with the processors
hook option. See the hook options section for more details.
You can disable manual verification of signatures by setting verify_signatures
to false
. See the Security section for more information on this option and what exactly it controls.
Currently, Overcommit supports the following hooks out of the box—simply enable them in your .overcommit.yml
.
Note: Hooks with a *
are enabled by default.
Warning: This list represents the list of hooks available on the master
branch. Please consult the change log to view which hooks have not been released yet.
commit-msg
hooks are run against every commit message you write before a commit is created. A failed hook prevents a commit from being created. These hooks are useful for enforcing policies on your commit messages, e.g. ensuring a task ID is included for tracking purposes, or ensuring your commit messages follow proper formatting guidelines.
*
CapitalizedSubject*
EmptyMessage*
SingleLineSubject*
TextWidth*
TrailingPeriodpost-checkout
hooks run after a successful git checkout
, or in other words any time your HEAD
changes or a file is explicitly checked out.
post-commit
hooks run after a commit is successfully created. A hook failing in this case does not prevent the commit since it has already occurred; however, it can be used to alert the user to some issue.
post-merge
hooks run after a git merge
executes successfully with no merge conflicts. A hook failing in this case does not prevent the merge since it has already occurred; however, it can be used to alert the user to some issue.
post-rewrite
hooks run after a commit is modified by a git commit --amend
or git rebase
. A hook failing in this case does not prevent the rewrite since it has already occurred; however, it can be used to alert the user to some issue.
pre-commit
hooks are run after git commit
is executed, but before the commit message editor is displayed. If a hook fails, the commit will not be created. These hooks are ideal for syntax checkers, linters, and other checks that you want to run before you allow a commit to even be created.
pre-commit
hooks currently do not support hooks with side effects (such as modifying files and adding them to the index with git add
). This is a consequence of Overcommit's pre-commit hook stashing behavior to ensure hooks are run against only the changes you are about to commit.
Without Overcommit, the proper way to write a pre-commit
hook would be to extract the staged changes into temporary files and lint those files instead of whatever contents are in your working tree (as you don't want unstaged changes to taint your results). Overcommit takes care of this for you, but to do it in a generalized way introduces this limitation. See the thread tracking this issue for more details.
*
AuthorEmail*
AuthorName*
BrokenSymlinks*
CaseConflicts*
MergeConflictspre-push
hooks are run during git push
, after remote refs have been updated but before any objects have been transferred. If a hook fails, the push is aborted.
pre-rebase
hooks are run during git rebase
, before any commits are rebased. If a hook fails, the rebase is aborted.
Out of the box, overcommit
comes with a set of hooks that enforce a variety of styles and lints. However, some hooks only make sense in the context of a specific repository.
For example, you can have a number of simple checks that run against your code to catch common errors. For example, if you use RSpec, you can make sure all spec files contain the line require 'spec_helper'
.
Inside our repository, we can add the file .git-hooks/pre_commit/ensure_spec_helper.rb
in order to automatically check our spec files:
module Overcommit::Hook::PreCommit
class EnsureSpecHelper < Base
def run
errors = []
applicable_files.each do |file|
if File.read(file) !~ /^require 'spec_helper'/
errors << "#{file}: missing `require 'spec_helper'`"
end
end
return :fail, errors.join("\n") if errors.any?
:pass
end
end
end
The corresponding configuration for this hook would look like:
PreCommit:
EnsureSpecHelper:
enabled: true
description: 'Checking for missing inclusion of spec_helper'
include: '**/*_spec.rb'
You might already have hook scripts written which you'd like to integrate with Overcommit right away. To make this easy, Overcommit allows you to include your hook script in your configuration without writing any Ruby code. For example:
PostCheckout:
CustomScript:
enabled: true
required_executable: './bin/custom-script'
So long as a command is given (either by specifying the command
option directly or specifying required_executable
) a special hook is created that executes the command and appends any arguments and standard input stream that would have been passed to the regular hook. The hook passes or fails based on the exit status of the command.
The script is executed as if Git were calling the hook directly. If you want to understand which arguments are passed to the script depending on the type of hook, see the git-hooks documentation.
While Overcommit can make managing Git hooks easier and more convenient, this convenience can come at a cost of being less secure.
Since installing Overcommit hooks will allow arbitrary plugin code in your repository to be executed, you expose yourself to an attack where checking out code from a third party can result in malicious code being executed on your system.
As an example, consider the situation where you have an open source project. An attacker could submit a pull request which adds a post-checkout
hook that executes some malicious code. When you fetch and checkout this pull request, the post-checkout
hook will be run on your machine, along with the malicious code that you just checked out.
Overcommit attempts to address this problem by storing a signature of your configuration and all hook plugin code since the last time it ran. When the signature changes, a warning is displayed alerting you to which plugins have changed. It is then up to you to manually verify that the changes are not malicious, and then continue running the hooks.
The signature is derived from the contents of the plugin's source code itself and any configuration for the plugin. Thus a change to the plugin's source code or your local repo's .overcommit.yml
file could result in a signature change.
In typical usage, your plugins usually don't change too often, so this warning shouldn't become a nuisance. However, users who work within proprietary repositories where all developers who can push changes to the repository already have a minimum security clearance may wish to disable this check.
While not recommended, you can disable signature verification by setting verify_signatures
to false
in your .overcommit.yml
file.
Regardless of whether you have verify_signatures
disabled for your project, if you are running Overcommit for the first time you will need to sign your configuration with overcommit --sign
. This needs to happen once so Overcommit can record in your local git repo's configuration (outside of source control) that you intend to enable/disable verification. This way if someone else changes verify_signatures
you'll be asked to confirm the change.
We love contributions to Overcommit, be they bug reports, feature ideas, or pull requests. See our guidelines for contributing to best ensure your thoughts, ideas, or code get merged.
All major discussion surrounding Overcommit happens on the GitHub issues list.
If you're interested in seeing the changes and bug fixes between each version of overcommit
, read the Overcommit Changelog.
Author: SDS
Source Code: https://github.com/sds/overcommit
License: MIT License
1648524659
The React useCallback hook can help you improve performance of your React apps. Learn about how React useCallback works and how to use it.
The React useCallback hook can help you improve performance of your React apps. It is weird that useCallback hook is one of the hooks that are not discussed as often. In this tutorial, you will learn about what React useCallback is, how it works and how to use it. You will also learn a bit about memoization.
The main purpose of React useCallback hook is to memoize functions. The main reason for this is increasing performance of your React applications. How is this related? Every time your component re-renders it also re-creates functions that are defined inside it. Memoizing functions helps you prevent this.
When you memoize a function with useCallback hook that function is basically stored in cache. Quick example. Imagine that something causes your component to re-render. Let’s say there is a change of state. Usually, this re-render would, by default, also cause React to re-create all functions defined in your component.
This may not happen with useCallback hook and memoization. When you memoize a function React may not re-create that function just because the component re-rendered. Instead, React can skip the re-creation and return the memoized function. This can help you save resources and time and improve performance of your application.
If you already know the React useEffect hook you will find the syntax of useCallback familiar. They are actually almost the same. Similarly to useEffect hook, useCallback also accepts two parameters. The first parameter is the function you want to memoize. The second parameter is an array of dependencies.
This array of dependencies specifies values React should watch. When any of these values changes, React should re-create the function. Otherwise, it should return the memoized version of the function.
// Import useCallback hook from React:
import { useCallback } from 'react'
export default function App() {
// Use useCallback to memoize function:
const memoizedFunc = useCallback(() => {
someFunction() // Function that will be memoized.
}, [/* depOne, depTwo, ...dep */]) // <= Dependency array.
// A bit shorter version:
const memoizedFunc = useCallback(() => someFunction(), [])
return (
<div className="App">
{/* Your component */}
</div>
)
}
The array of dependencies is important. It helps React understand when to return the memoized function and when to re-create it. Why re-create it? Wasn’t the purpose of memoization preventing this from happening? Well, yes and no. Yes, you want to prevent the function from being re-created.
However, if the function depends on some input, you want to re-create that function when the input changes. Otherwise, you would execute the function with old input that is no longer relevant. For example, let’s say you have a function that greets the user using their name.
This function will depend on the name of the current user. If you memoize it the first time you create it, it will remember the first name. When the name changes, it will not register it. It will greet every subsequent user using the first name. Solution for this is adding the name as a dependency.
When you specify the name as dependency, React will automatically re-create the function when the name changes. When new user arrives, and the name changes, the function will be re-created. It will update its input, use the latest value of name, and greet the user using a correct name.
Let’s demonstrate the power of dependencies and memoization on a simple example. Imagine you have a simple component that contains input and button. The input allows the user to specify her name. This name will be stored in local state created with useState hook. Click on the button logs the name to the console.
The handler function for the button will be memoized with useCallback hook. On the first try, you forget to include the name as a dependency for the hook. What you do instead is specify the dependency array as an empty array. This tells React that it should create the function only on the initial render.
When something happens that causes a subsequent re-render of the component, it should return the memoized version of the function. Remember that changing state causes React to re-render. This helps keep everything in sync. What happens when the user write her name in the input and clicks the button?
The user will be probably surprised. The console will show the initial value of the “name” state. The reason is that when the function was created, the value of name was the initial value. When the name changed React didn’t re-create the function and the function didn’t know the name has changed.
// Note: this will not work as you may expect:
// Import useCallback and useState hooks from React.
import { useCallback, useState } from 'react'
export default function App() {
// Create state for name:
const [name, setName] = useState('')
// Create and memoize function for logging name:
const handleShowName = useCallback(() => {
console.log(name)
}, []) // <= Notice the empty array with dependencies.
// Each click on the button will log
// the initial value of "name" state, i.e. the ''.
return (
<div className="App">
{/* Change "name" state when input changes: */}
<input value={name} onChange={(event) => setName(event.target.value)} />
{/* Attach handleShowName function */}
<button onClick={handleShowName}>Show name</button>
</div>
)
}
A simple way to fix this is adding the “name” state as a dependency. Now, React will watch this value and re-create the function whenever the name changes. This will ensure that when the user changes the name the function will always have the latest information and log correct value.
// Note: this will not work as you may expect:
import { useCallback, useState } from 'react'
export default function App() {
// Create state for name
const [name, setName] = useState('')
// Create and memoize function for logging name:
const handleShowName = useCallback(() => {
console.log(name)
}, [name]) // <= Add "name" state as dependency.
return (
<div className="App">
{/* Change name state when input changes: */}
<input value={name} onChange={(event) => setName(event.target.value)} />
{/* Attach handleShowName function */}
<button onClick={handleShowName}>Show name</button>
</div>
)
}
The array of dependency, the second parameter, tells React when memoized function should be re-created. There are basically three options.
First, React can re-create the function after every render of your component. This pretty much defeats the whole purpose of useCallback hook, but it is still something you can do. For this, all you have to do is to omit the dependencies array. Use useCallback hook only with the function you want to memoize.
// Import useCallback hook from React:
import { useCallback } from 'react'
export default function App() {
// Use useCallback to memoize function:
const memoizedFunc = useCallback(() => someFunction())
// Omit the dependency parameter (array).
return (
<div className="App">
{/* Your component */}
</div>
)
}
If you really want to do this, you can simply skip using the useCallback hook. This option will lead to same result as declaring a function without the useCallback hook. The function will be re-created on every re-render and never memoized.
// Import useCallback hook from React:
import { useCallback } from 'react'
export default function App() {
// Normal function:
const someFunction = () => (/* Do something */)
return (
<div className="App">
{/* Your component */}
</div>
)
}
The second option is to create the function only after the initial render. When a subsequent re-render happens, React will return the memoized version of the function. This can be useful in two cases. First, when the function should always return the same result and probably doesn’t work with external input.
The second case is when the function works with external input(s), but that input doesn’t change. If the input doesn’t change or the function doesn’t depend on any external input, you may think about memoizing it. To do this, pass an empty array as the dependency parameter.
// Import useCallback hook from React:
import { useCallback } from 'react'
export default function App() {
// Use useCallback to memoize function:
const memoizedFunc = useCallback(() => someFunction(), [])
// Pass an empty array as dependency parameter.
return (
<div className="App">
{/* Your component */}
</div>
)
}
The last option is to re-create the function when only specific value or values change. If some of the values change React will re-create the function to ensure it has the latest data. Otherwise, it will return the memoized version of the function. For this, specify the values you want to watch in the dependency array as a parameter.
From now, when any of these watched values change React will automatically re-create the function. Otherwise, it will return the memoized version. Remember that only one value you specified as a dependency has to change for React to re-create the function, not all of them.
// Import useCallback hook from React:
import { useCallback, useState } from 'react'
export default function App() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [isValid, setIsValid] = useState(false)
// Create and memoize form handler
const handleFormSubmit = useCallback(
() => {
// Submit form.
},
[name, email, isValid], // <= Watch "name", "email" and "isValid".
)
return (
<form className="App">
{/* Your form component */}
<button onClick={handleFormSubmit}></button>
</form>
)
}
Just because there is some tool doesn’t mean you have to use it. The same also applies to React useCallback hook. The purpose of this hook is to improve performance of heavy components. It is not intended to be a default “wrapper” for every single function you declare in your component.
So, don’t assume that you have to use useCallback every time you declare a function. You don’t. Use this hook in heavy components that use multiple functions and these functions don’t have to be re-created on every render. Even then, consider the potential gain and loses.
Will memoization help you measurably improve performance? Or, will it only introduce more complexity to your code, while any performance gains will be barely noticeable? For small and light components useCallback might not make a difference.
The React useCallback hook can be useful for improving performance of your apps, by storing your functions for later use, instead of re-creating them on every re-render. This can improve re-rendering behavior and performance of heavy components. I hope this tutorial helped you understand how useCallback hook works and how to use it.
Original article source at https://blog.alexdevero.com
#react #hook #reacthook #programming
1647791940
Flutter Hooks
Hooks are a new kind of object that manage the life-cycle of a Widget
. They exist for one reason: increase the code-sharing between widgets by removing duplicates.
StatefulWidget
suffers from a big problem: it is very difficult to reuse the logic of say initState
or dispose
. An obvious example is AnimationController
:
class Example extends StatefulWidget {
final Duration duration;
const Example({Key key, required this.duration})
: super(key: key);
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
AnimationController? _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: widget.duration);
}
@override
void didUpdateWidget(Example oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.duration != oldWidget.duration) {
_controller!.duration = widget.duration;
}
}
@override
void dispose() {
_controller!.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
All widgets that desire to use an AnimationController
will have to reimplement almost all of this logic from scratch, which is of course undesired.
Dart mixins can partially solve this issue, but they suffer from other problems:
This library proposes a third solution:
class Example extends HookWidget {
const Example({Key key, required this.duration})
: super(key: key);
final Duration duration;
@override
Widget build(BuildContext context) {
final controller = useAnimationController(duration: duration);
return Container();
}
}
This code is functionally equivalent to the previous example. It still disposes the AnimationController
and still updates its duration
when Example.duration
changes. But you're probably thinking:
Where did all the logic go?
That logic has been moved into useAnimationController
, a function included directly in this library (see Existing hooks) - It is what we call a Hook.
Hooks are a new kind of object with some specificities:
They can only be used in the build
method of a widget that mix-in Hooks
.
The same hook can be reused arbitrarily many times. The following code defines two independent AnimationController
, and they are correctly preserved when the widget rebuild.
Widget build(BuildContext context) {
final controller = useAnimationController();
final controller2 = useAnimationController();
return Container();
}
Hooks are entirely independent of each other and from the widget.
This means that they can easily be extracted into a package and published on pub for others to use.
Similar to State
, hooks are stored in the Element
of a Widget
. However, instead of having one State
, the Element
stores a List<Hook>
. Then in order to use a Hook
, one must call Hook.use
.
The hook returned by use
is based on the number of times it has been called. The first call returns the first hook; the second call returns the second hook, the third call returns the third hook and so on.
If this idea is still unclear, a naive implementation of hooks could look as follows:
class HookElement extends Element {
List<HookState> _hooks;
int _hookIndex;
T use<T>(Hook<T> hook) => _hooks[_hookIndex++].build(this);
@override
performRebuild() {
_hookIndex = 0;
super.performRebuild();
}
}
For more explanation of how hooks are implemented, here's a great article about how is was done in React: https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e
Due to hooks being obtained from their index, some rules must be respected:
use
:Widget build(BuildContext context) {
// starts with `use`, good name
useMyHook();
// doesn't start with `use`, could confuse people into thinking that this isn't a hook
myHook();
// ....
}
Widget build(BuildContext context) {
useMyHook();
// ....
}
use
into a conditionWidget build(BuildContext context) {
if (condition) {
useMyHook();
}
// ....
}
Since hooks are obtained from their index, one may think that hot-reloads while refactoring will break the application.
But worry not, a HookWidget
overrides the default hot-reload behavior to work with hooks. Still, there are some situations in which the state of a Hook may be reset.
Consider the following list of hooks:
useA();
useB(0);
useC();
Then consider that we edited the parameter of HookB
after performing a hot-reload:
useA();
useB(42);
useC();
Here everything works fine and all hooks maintain their state.
Now consider that we removed HookB
. We now have:
useA();
useC();
In this situation, HookA
maintains its state but HookC
gets hard reset. This happens because, when a hot-reload is perfomed after refactoring, all hooks after the first line impacted are disposed of. So, since HookC
was placed after HookB
, it will be disposed.
There are two ways to create a hook:
A function
Functions are by far the most common way to write hooks. Thanks to hooks being composable by nature, a function will be able to combine other hooks to create a more complex custom hook. By convention, these functions will be prefixed by use
.
The following code defines a custom hook that creates a variable and logs its value to the console whenever the value changes:
ValueNotifier<T> useLoggedState<T>([T initialData]) {
final result = useState<T>(initialData);
useValueChanged(result.value, (_, __) {
print(result.value);
});
return result;
}
A class
When a hook becomes too complex, it is possible to convert it into a class that extends Hook
- which can then be used using Hook.use
.
As a class, the hook will look very similar to a State
class and have access to widget life-cycle and methods such as initHook
, dispose
and setState
.
It is usually good practice to hide the class under a function as such:
The following code defines a hook that prints the total time a State
has been alive on its dispose.
class _TimeAlive extends Hook<void> {
const _TimeAlive();
@override
_TimeAliveState createState() => _TimeAliveState();
}
class _TimeAliveState extends HookState<void, _TimeAlive> {
DateTime start;
@override
void initHook() {
super.initHook();
start = DateTime.now();
}
@override
void build(BuildContext context) {}
@override
void dispose() {
print(DateTime.now().difference(start));
super.dispose();
}
}
Result useMyHook() {
return use(const _TimeAlive());
}
Flutter_Hooks already comes with a list of reusable hooks which are divided into different kinds:
A set of low-level hooks that interact with the different life-cycles of a widget
Name | Description |
---|---|
useEffect | Useful for side-effects and optionally canceling them. |
useState | Creates a variable and subscribes to it. |
useMemoized | Caches the instance of a complex object. |
useRef | Creates an object that contains a single mutable property. |
useCallback | Caches a function instance. |
useContext | Obtains the BuildContext of the building HookWidget . |
useValueChanged | Watches a value and triggers a callback whenever its value changed. |
This category of hooks the manipulation of existing Flutter/Dart objects with hooks. They will take care of creating/updating/disposing an object.
Name | Description |
---|---|
useStream | Subscribes to a Stream and returns its current state as an AsyncSnapshot . |
useStreamController | Creates a StreamController which will automatically be disposed. |
useFuture | Subscribes to a Future and returns its current state as an AsyncSnapshot . |
Name | Description |
---|---|
useSingleTickerProvider | Creates a single usage TickerProvider . |
useAnimationController | Creates an AnimationController which will be automatically disposed. |
useAnimation | Subscribes to an Animation and returns its value. |
Name | Description |
---|---|
useListenable | Subscribes to a Listenable and marks the widget as needing build whenever the listener is called. |
useValueNotifier | Creates a ValueNotifier which will be automatically disposed. |
useValueListenable | Subscribes to a ValueListenable and return its value. |
A series of hooks with no particular theme.
Name | Description |
---|---|
useReducer | An alternative to useState for more complex states. |
usePrevious | Returns the previous argument called to [usePrevious]. |
useTextEditingController | Creates a TextEditingController . |
useFocusNode | Createx a FocusNode . |
useTabController | Creates and disposes a TabController . |
useScrollController | Creates and disposes a ScrollController . |
usePageController | Creates and disposes a PageController . |
useAppLifecycleState | Returns the current AppLifecycleState and rebuilds the widget on change. |
useOnAppLifecycleStateChange | Listens to AppLifecycleState changes and triggers a callback on change. |
useTransformationController | Creates and disposes a TransformationController . |
useIsMounted | An equivalent to State.mounted for hooks. |
Contributions are welcomed!
If you feel that a hook is missing, feel free to open a pull-request.
For a custom-hook to be merged, you will need to do the following:
Describe the use-case.
Open an issue explaining why we need this hook, how to use it, ... This is important as a hook will not get merged if the hook doesn't appeal to a large number of people.
If your hook is rejected, don't worry! A rejection doesn't mean that it won't be merged later in the future if more people show interest in it. In the mean-time, feel free to publish your hook as a package on https://pub.dev.
Write tests for your hook
A hook will not be merged unless fully tested to avoid inadvertendly breaking it in the future.
Add it to the README and write documentation for it.
A Flutter implementation of React hooks: https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889
Author: rrousselGit
Source Code: https://github.com/rrousselGit/flutter_hooks
License: MIT License
1647420494
The React useReducer hook is very useful when you need to manage complex states. Learn about how useReducer hook works and how to use it.
The React useReducer hook is a very good alternative to useState when you need to manage complex states with multiple values. In this tutorial, you will learn about this React hook. You will learn about how useReducer hook works. You will also learn how to use it to manage state.
The React useReducer hook is quite similar to useState hook. Like the useState hook, it also allows you to manage state of your React applications. The advantage of useReducer is that it makes it easier to work with complex states. By complex state I mean state with multiple sub-values, an object with key-value pairs.
The useReducer hook makes this easier by using more structural approach. That said, this doesn’t mean that you should useReducer hook only to deal with such states. You can useReducer just as well with simple states that contain single primitive value. The way useReducer hook works is simple.
It uses two pieces of data, state and reducer function. The reducer is a pure function that takes a state and an action. The actions tells the reducer what you want it to do. What is the update you want to do to the state. For example, increment number, decrement number, push new value to array, etc.
Reducer function takes these inputs, applies the action you specified, and returns a new state value. This new state value is an updated version of the state you provided it with. Something to remember. Reducer should not change the old one directly. About the syntax.
About “pure” functions. A function is pure when it follows two rules. First, the function always returns the same output if you pass in the same arguments. Second, the function does not produce any side-effects. This means that the function has no effect on its surroundings.
Put simply, the function doesn’t work with the outside world. It works only with inputs you passed into it. A simple example of pure function can be a function that takes two numbers as parameters and returns their sum. If you pass in the same numbers, you will get the same result. This confirms the first rule.
The function doesn’t do anything with the code outside it. It works solely with those two numbers it gets as input. This confirms the second rule. We can say that the function is pure. Now, let’s say that the function stores the result in an outside variable. In this case, the function is not pure because it breaks the second rule.
When the function has an effect on outside world it is not pure. Changing variables outside it is such an effect. It would also not be pure if it logged the result or some message. These logs are also side-effects and thus break the second rule.
The React useReducer hook accepts three parameters. The first two parameters are required. These two are the reducer
and state
. The reducer
is the reducer function we discussed above. The state
is any initial state value. This is the same initial state you know from working with useState
hook.
Aside to these two, the useReducer hooks also accepts third, optional parameter. This parameter is initializer
. This initializer
allows you to initialize the state lazily with a function. The result returned by this function becomes the initial state value.
This can be useful when you want to create initial state, but it involves some expensive operation, to generate the initial data. Just remember that React will invoke the initializer function only after the initial render, not after subsequent re-renders. That said, you will probably not need it as often.
The useReducer hook will return two things, or values. First is the current state. The second is a dispatch function. This function allows you to update the state you passed to the useReducer hook.
// useReducer hook syntax:
const [state, dispatch] = useReducer(reducer, initialState, init)
Before you can start using the useReducer hook you need two things, initial state and reducer function. Let’s start with the initial state. Initial state can be anything from primitive data type to object. Whatever fits your current situation. What you have to do is to create this state somewhere, and assign it to a variable.
// A simple initial state object:
const initialState = {
name: '',
email: '',
role: '',
isActive: false,
}
The second thing is the reducer function. The reducer function accepts two parameters: the state and action. It takes these two and updates the state, based on the action dispatched. It is very common to create the structure of reducer function, and handle each action, with switch statement.
The main reason is that switch is usually more readable than if...else
statement. Especially when you work with multiple actions. That said, if you prefer if...else
statement go ahead and use that. About the structure. The reducer has to have a case
, or if block, for each action you want to use to update the state.
Each of these actions should do two things. First, it should copy the current state. Reducer is a pure function. It is not supposed to change the existing state. What it does instead is it creates copies of it and works with them. It is common to create copies of old state by spreading the old, using spread.
The second thing reducer will do for each case, or block, is updating specific state value with the new value. Put together, it will basically copy the old state and overwrite only the values that should be updated. After that, it will return the new state. Aside to this there should be also a default
case or else block.
This case or block can do two things. First, it can return the original state unchanged. Second, it can throw an error about non-existing action. Similarly to initial state, you define the reducer as a function somewhere in your code. Don’t pass it to the reducer as a whole.
// Create reducer function:
const reducer = (state, action) => {
// Create switch to handle all actions:
switch (action.type) {
case 'SET_NAME':
// Handle 'SET_NAME' action:
return {
...state, // Copy the old state.
name: action.payload // Update relevant value.
}
case 'SET_EMAIL':
// Handle 'SET_EMAIL' action:
return {
...state, // Copy the old state.
email: action.payload // Update relevant value.
}
case 'SET_ROLE':
// Handle 'SET_ROLE' action:
return {
...state, // Copy the old state.
role: action.payload // Update relevant value.
}
case 'SET_IS_ACTIVE':
// Handle 'SET_IS_ACTIVE' action:
return {
...state, // Copy the old state.
isActive: action.payload // Update relevant value.
}
default:
// Throw an error when none of cases matches the action.
throw new Error('Unexpected action')
}
}
In the reducer function example you could see action.type
and action.payload
. This is because when you update the state with dispatch function returned by the useReducer hook you pass in an object. This object contains two keys, type
and payload
. The type
tell reducer function what action you want to make.
Reducer function then uses this information, the type
, to use one of the switch
cases, or if blocks. The payload
is where you put the new value for the state. These two names are not mandatory. They are just a common practice among React developers. You can use any names you want. Just make sure to use correct names in your reducer.
// Dispatched object example to set name:
dispatch({
type: 'SET_NAME',
payload: 'Victor'
})
// Dispatched object example to set role:
dispatch({
type: 'SET_ROLE',
payload: 'Admin'
})
// Dispatched object example to set isActive:
dispatch({
type: 'SET_IS_ACTIVE',
payload: true
})
You have the initial state and reducer function. Now, you can use them with the useReducer hook and let the hook handle state management for you. The process is simple. Call the useReducer hook and pass in the reducer function and initial state, in this order. This will return the state
and dispatch
function.
When you want to update specific state value you use the dispatch
function. You call this function passing an object as an argument. This is the action
. This object will contain two keys, type
and payload
(or any names you chose). The type
must match one of the switch
cases in your reducer function.
The value of payload is the value you want to update the state with. It is the new value you want to store in the state. The state
value returned by the useReducer hook will always give you the latest values of the state. This is just like when you use useState hook. In this case, the state
is still the same. The state updater function is the dispatch
.
// Import useReducer hook from React:
import { useReducer } from 'react'
// Create initial state:
const initialState = {
name: '',
email: '',
role: '',
isActive: false,
}
// Create reducer function:
const reducer = (state, action) => {
// Create switch to handle all actions:
switch (action.type) {
case 'SET_NAME':
// Handle 'SET_NAME' action:
return {
...state, // Copy the old state.
name: action.payload // Update relevant value.
}
case 'SET_EMAIL':
// Handle 'SET_EMAIL' action:
return {
...state, // Copy the old state.
email: action.payload // Update relevant value.
}
case 'SET_ROLE':
// Handle 'SET_ROLE' action:
return {
...state, // Copy the old state.
role: action.payload // Update relevant value.
}
case 'SET_IS_ACTIVE':
// Handle 'SET_IS_ACTIVE' action:
return {
...state, // Copy the old state.
isActive: action.payload // Update relevant value.
}
default:
// Throw an error when none of cases matches the action.
throw new Error('Unexpected action')
}
}
// Create simple component:
export default function App() {
// Call useReducer hook, passing in
// previously created reducer function
// and initial state:
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div className="App">
{/*
Create input for "name" and use dispatch
to update "name" state value on input change.
*/}
<input
type="text"
name="name"
value={state.name}
onChange={(event) => dispatch({
type: 'SET_NAME', // Dispatch 'SET_NAME' action.
payload: event.target.value // Set input value as payload.
})}
/>
{/*
Create input for "email" and use dispatch
to update "email" state value on input change.
*/}
<input
type="email"
name="email"
value={state.email}
onChange={(event) => dispatch({
type: 'SET_EMAIL', // Dispatch 'SET_EMAIL' action.
payload: event.target.value // Set input value as payload.
})}
/>
{/*
Create select for selecting "role" and use dispatch
to update "role" state value on select change.
*/}
<select
onChange={(event) => dispatch({
type: 'SET_ROLE', // Dispatch 'SET_ROLE' action.
payload: event.target.value // Set input value as payload.
})}
>
<option value="" selected></option>
<option value="Admin">Admin</option>
<option value="User">User</option>
<option value="guest">Guest</option>
</select>
{/*
Create checkbox for isActive and use dispatch
to update "isActive" state value on checkbox change.
*/}
<label>
<input
type="checkbox"
checked={state.isActive}
onChange={(event, checked) => dispatch({
type: 'SET_IS_ACTIVE', // Dispatch 'SET_IS_ACTIVE' action.
payload: checked // Set checkbox checked value as payload.
})}
/>
Is active?
</label>
</div>
)
}
The React useReducer hook is a good alternative to useState hook. Where useReducer can be very useful is when you have to deal with complex states. In these situations, useReducer might be a better choice than useState. I hope that this tutorial helped you understand how the React useReducer hook works and how to use it.
Original article source at https://blog.alexdevero.com
#react #programming #javascript #hook
1646485560
hook-std
Hook and modify stdout and stderr
npm install hook-std
import assert from 'node:assert';
import {hookStdout} from 'hook-std';
const promise = hookStdout(output => {
promise.unhook();
assert.strictEqual(output.trim(), 'unicorn');
});
console.log('unicorn');
await promise;
You can also unhook using the second transform
method parameter:
import assert from 'node:assert';
import {hookStdout} from 'hook-std';
const promise = hookStdout((output, unhook) => {
unhook();
assert.strictEqual(output.trim(), 'unicorn');
});
console.log('unicorn');
await promise;
Hook streams in streams
option, or stdout and stderr if none are specified.
Returns a Promise
with a unhook()
method which, when called, unhooks both stdout and stderr and resolves the Promise
with an empty result.
Hook stdout.
Returns a Promise
with a unhook()
method which, when called, unhooks stdout and resolves the Promise
with an empty result.
Hook stderr.
Returns a Promise
with a unhook()
method which, when called, unhooks stderr and resolves the Promise
with an empty result.
Type: object
silent
Type: boolean
Default: true
Suppress stdout/stderr output.
once
Type: boolean
Default: false
Automatically unhook after the first call.
streams
Type: stream.Writable[]
Default: [process.stdout, process.stderr]
The writable streams to hook. This can be useful for libraries allowing users to configure a writable stream to write to.
Type: Function
Receives stdout/stderr as the first argument and the unhook method as the second argument. Return a string to modify it. Optionally, when in silent mode, you may return a boolean
to influence the return value of .write(…)
.
Author: Sindresorhus
Source Code: https://github.com/sindresorhus/hook-std
License: MIT License
1645775776
React offers a couple of built-in hooks. You can also create your React custom hooks. Learn what custom hooks are and how to create them.
React offers a number of built-in hooks you can use right away. Aside to these, you can also create your own custom hooks. In this tutorial, you will learn what React custom hooks are and how to create your own. You will also learn what rules you have to follow when creating custom hooks.
It was in React v16.8 when React hooks were introduced by React team. Since then, hooks quickly rose in popularity among React developers, and even beyond. Until then, when you wanted to use state and lifecycle methods inside React components you had to use JavaScript classes.
React hooks changed this paradigm. With hooks, you no longer have to create components with classes just so you can use state. You can just as well create functional components. Then, you can use hooks to “enhance” these components with whatever feature you need, be it a state, lifecycle method or something else.
The word “hook” may sound a bit vague. To make it easier, think about hooks simply as functions. This is what hooks are, plain JavaScript functions. These functions allow you “hook into” various React features, such as state and lifecycle. Most popular examples are useState and useEffect hooks.
The useState hooks allows you to bring state to function components. With useEffect hook, you can work with component lifecycle. There is a couple of React hooks ready to use. However, these hooks can’t do everything. They can’t cover every possible scenario. Fortunately, these hooks are not the only option.
Aside to releasing a number of official React hooks, React team made it also possible to create React custom hooks. So, if you can’t find a hook that would solve some problem you have, you can create your own. You can also use React custom hooks to make your code that involves stateful logic reusable.
As we already discussed, hooks are basically plain JavaScript functions. One difference is that hooks are used for a specific purpose. Another is that React hooks allow you to use other React hooks, such as useState, and even other custom hooks. So, don’t worry that creating custom hooks will be difficult.
Creating custom hooks will be similar to writing regular JavaScript functions. You will probably get a grasp on it quickly. It will be even faster if you know how to use hooks such as useState and useEffect because you are likely to use these hooks in your custom hooks. But before we get into that, there are some rules to learn.
Before you create your first custom hook, there are two rules you should know. These rules are called Rules of hooks. First, you can call hooks only at the top level. Never call hooks inside nested functions, conditions or loops. If you want to use conditions, or loops, use them inside the hook, not the other way around.
The second rule is that you should call hooks only from React function components or other hooks. There is also one practice for creating custom hooks. Always start the name of the hook with “use” prefix. This is more like a good rule of thumb than a rule. It is good to follow to make code more readable, but it is not required.
React custom hooks are JavaScript functions. This means few things. First, when you create a custom hook you are writing a function. Second, function name should start with “use”. Remember, this is a good rule of thumb for creating custom hooks. Third, you can use other React hooks inside your custom hooks.
These are the things to remember. To make this more hands-on, let’s put this together and create few examples of React custom hooks. This can make it easier to understand how custom hooks work and how to create them.
The first example will be a hook that will return current window size. First, the name. The name should be descriptive and start with “use”. Something like “useWindowSize” sounds like a good candidate. Second, the logic of the hook. When you call this hook, it will do few things.
The first thing it will do is getting the current window size and returning it. Second, it will attach event listener to window
object and listen to resize
event. When this event happens, the hook will detect the new window size and return it again. This will repeat every time the resize
event happens.
Custom hooks can use other React hooks. This means that we can use useState hook to store the latest window dimension in a state and return the value of this state. We can also use useEffect hook to attach the event listener for resize
event. We can use this useEffect hook to remove the event listener.
We can do this by returning a clean up function. This function will call the removeEventListener
method, passing the resize
event and function for handling the resize.
// Import useEffect and useState hooks from React:
import { useEffect, useState } from 'react'
// Create custom useWindowSize hook function:
export function useWindowSize() {
// Create function to get current window size:
const getWindowSize = () => ({
innerHeight: window.innerHeight,
innerWidth: window.innerWidth,
outerHeight: window.outerHeight,
outerWidth: window.outerWidth,
})
// Create state for window size data:
const [windowSize, setWindowSize] = useState(getWindowSize())
// It also uses the getWindowSize() to set the initial state.
// Create handler function for resize event:
function handleResize() {
// Update state value:
setWindowSize(getWindowSize())
}
// Create a side-effect
useEffect(() => {
// Attach event listener for "resize" event:
window.addEventListener('resize', handleResize)
return () => {
// Remove event listener for "resize" event:
window.removeEventListener('resize', handleResize)
}
}, [])
// Return current window size:
return windowSize
}
When you want to use this hook, import it in your React component and call it. Remember to assign that call to variable so that you can get the window size every time it changes.
// Import the useWindowSize hook:
import { useWindowSize } from './hook'
export default function App() {
// Call the useWindowSize hook and assign it to variable:
const windowSize = useWindowSize()
// Display the current window size:
return (
<div>
<ul>
<li>window inner width: {windowSize.innerWidth}</li>
<li>window inner height: {windowSize.innerHeight}</li>
<li>window outer width: {windowSize.outerWidth}</li>
<li>window outer height: {windowSize.outerHeight}</li>
</ul>
</div>
)
}
Another simple, but useful hook can be hook for managing toggle state. Such a hook could be useful for creating collapsible components for example. It could help you check for current toggle state and switching between “on” and “off” state. It could also allow to reset the state or set it manually.
This hook will be simple. It will use useState hook to store toggle state. Aside to this, it will have two functions, handleReset
and handleToggle
. The handleReset
will reset the toggle state to the initial value. The handleToggle
will reverse current toggle state. It switch from “on” to “off” and the other way around.
The value we will return from this hook will be an object. This object will contain all these methods and current value of the toggle state. We will also return the setter method for state to allow setting custom state. When you import this hook, you will be able to import anything inside the object it returns.
// Import useEffect and useState hooks from React:
import { useState } from 'react'
// Create custom useToggle hook function:
export function useToggle(initialState = false) {
// Create toggle state:
const [toggle, setToggle] = useState(initialState)
// Create handler function for resetting the state:
const handleReset = () => setToggle(initialState)
// Create handler function for toggling the state:
const handleToggle = () => setToggle(prevState => !prevState)
// Return the state, state setter function and handler functions:
return {
on: toggle,
set: setToggle,
reset: handleReset,
toggle: handleToggle
}
}
Just like with the previous hook, you can now import this useToggle in your React component. When you call it, you can use destructuring assignment to assign anything from the object this hook returns to a variable so you can use it.
// Import the useToggle hook:
import { useToggle } from './hook'
export default function App() {
// Call the useToggle hook and assign variables,
// using destructuring assignment:
const { on, set, reset, toggle } = useToggle()
// Use any method or state returned from the hook:
return (
<div>
<p>On: {on ? 'true' : 'false'}</p>
<button onClick={() => set(true)}>Set to on</button>
<button onClick={reset}>Reset</button>
<button onClick={toggle}>Toggle</button>
</div>
)
}
Third and last example. It became popular to store application or website data in local or session storage. This hook will accept two parameters: name of the key to store and initial value for this key. When called, this hook will first check if local storage is available in the browser.
If local storage is not available, it will return the initial value passed as argument. If local storage is available, it will check if any key with the same name exists. If it does, it will retrieve its data. Otherwise, it will return the initial value. Whatever is returned will become the state of the hook.
This all will happen during the initial load. It will happen inside initializer function for useState hook. The second part of the hook will be a handler function for storing data in local storage. This function will accept one parameter, the data to store. It will first take this value and store it inside the hook state.
Then, it will store the value in local storage. The name of the key for this data will be the name of the key passed to the hook during the call. The last part, returning something. This hook will return two things: current value of the state, data loaded from local storage, and handler function for storing data in local storage.
// Import useState hook from 'react':
import { useState } from 'react'
export function useLocalStorage(keyName, initialValue) {
// Create state for local storage:
const [storedValue, setStoredValue] = useState(() => {
try {
// Check if local storage is available:
if (typeof window === 'undefined') {
return initialValue
}
// Check if item with the same name exists in local storage:
const item = window.localStorage.getItem(keyName)
// Return parsed data from storage or return the initialValue:
return item !== null ? JSON.parse(item) : initialValue;
} catch (error) {
// Catch any errors and log them:
console.log(error)
// Return the initialValue:
return initialValue
}
})
// Create handler function for storing value in local storage:
const setValue = (value) => {
try {
// Store the value in the state:
setStoredValue(value)
// Store the value in local storage:
window.localStorage.setItem(keyName, JSON.stringify(value))
} catch (error) {
// Catch any errors and log them:
console.log(error)
}
}
// Return latest data and handler function for storing new data:
return [storedValue, setValue]
}
The way to use this hook will be similar to using useState. When you call it, you pass in the name of the key and data for that key. The hook will return array with the data and handler function for storing new data. The data returned will be either the initial value or any data that is already stored in local storage for that key.
// Import the useLocalStorage hook:
import { useLocalStorage } from './hook'
export default function App() {
// Call the useLocalStorage hook and assign variables,
// again, using destructuring assignment:
const [value, setValue] = useLocalStorage('name', 'Joe')
// Store data typed in the input in local storage
// and also display them in the DOM:
return (
<div>
<p>{value}</p>
<input type="text" onChange={(e) => setValue(e.currentTarget.value)} />
</div>
)
}
Official React hooks are very useful tools for every React developer. However, these official hooks can’t do everything you may want or need. Writing your own React custom hooks can help you solve this problem. I hope that this tutorial helped you learn what React custom hooks are, how they work and how to create your own.
Original article source at https://blog.alexdevero.com
#react #hook #javascript
1645516184
The React useContext hook allows to work with React context from anywhere in your app. Learn how to create new contexts and work with them.
React context makes it easy to create globally accessible data, and states. The useContext hook allows you to work with React contexts from anywhere and pass its data throughout your app. This tutorial will show you how to create new context, how to get values from it and how to change them.
When you work with data they are usually one of two types, global or local. Global can be accessed from anywhere. Local only from the place where they are defined, and down the tree. This also applies to states. You can have global states and you can have local states. Which one is the best choice depends on situation.
React context API makes it easy to create these global states. That said, there is one problem with these global states. They are often difficult to use in nested components. It can take a lot of prop drilling to get the data from the top to where you need them. You may have to pass these data through multiple components.
One way to solve this is making those data local. However, this would lead to duplicate code. It would also go against the idea of having one source of truth that is globally accessible. Another solution is to skip all prop drilling and simply reach to the context from the component where you need those data.
This is the goal of the React useContext hook. The React useContext hook promises to help you with two things. First, to help you reach out to any context and from anywhere. Second, to work with values exposed through this context. This includes both, getting those values as well as changing them. Let’s take a look at how it works.
Using React context requires getting done few things. First, you have to create a context. You achieve this by using createContext()
method shipped with React. This context will be the global state available for use across the app. Well, at least one them because your React app can contain infinite number of contexts.
// context.jsx
// Import createContext() method from React:
import { createContext } from 'react'
// Create new context:
export const newContext = createContext()
Notice that we are declaring the context as empty, basically assigning it undefined
. Don’t worry. This doesn’t mean this context will be empty forever. It will be empty just for now when you create it. Later, in the next step, you will add values to it. Also notice that we are exporting the context.
The reason for this is simple. The useContext hook accepts a context as a parameter. So, if we want to use the useContext hook to access the context anywhere in the app the context itself must be also accessible anywhere. This means we must export it from where it is.
The second thing you have to do is to create a provider for your new context. This provider is a component that provides your app with the value(s) stored inside the context. Provider wraps all components that should be able to access the context. This is important to remember.
Components will be able to communicate with provider only if they are provider’s children. It doesn’t matter where in the component tree they are. What matters is that the provider is used as a wrapper somewhere in the tree above. In general, provider is used as a wrapper for the entire app.
This guarantees that any component in the app will be able to communicate with the provider. If you have multiple providers, you can wrap one inside another while keeping the app as the last child. This will ensure the app has access to all providers up the tree. Now, let’s create the provider.
Creating the provider is similar to creating a regular React component. Nowadays, provider is usually created as a function component. You give this component some name. It is a good practice to end the name with “Provider”. It makes it easier to understand the code when you read it.
Inside this component, you can use any react hook you want. For example, you can use useState hook to create new state for the provider. You can then expose this state by setting it as a value for the provider. This will make it available for any component wrapped with the provider.
You can also use useCallback hook to create memoized functions. These functions can work with the state, update its values. You can also expose these functions by setting them as values for the provider. Again, this will make them available for components wrapped with the provider.
The most important part is where the rendering happens, what follows the return
statement. Here, you will use the context for the first time. The context you’ve previously created also contains a provider component your new provider will render. You can access this provider component using object dot notation (newContext.Provider
).
Since we want to use this provider as a wrapper, it should render any children it wraps.
// context.jsx
// Import createContext() method from React:
import { createContext } from 'react'
// Create new context:
export const newContext = createContext()
// Create new provider component:
export const NewProvider = (props) => {
return (
{/* Render Provider provided by previously created context: */}
<newContext.Provider>
{/* Render Provider's children: */}
{props.children}
</newContext.Provider>
)
}
Make sure to also export your new Provider component so you can use it where you need it. The next step is to take the Provider and use it as a wrapper for the components for which you want to make the data provided by this provider accessible. You can also use it to wrap the main app component.
This will make anything exposed by the provider accessible to any component in the app.
// index.jsx
// Import React and React-dom:
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
// Import the NewProvider component:
import { NewProvider } from './context'
// Import app component:
import App from './App'
// Create the main component:
const rootElement = document.getElementById('root')
ReactDOM.render(
<StrictMode>
{/* Use the NewProvider to wrap the whole app: */}
<NewProvider>
{/* The app component rendering all other components: */}
<App />
</NewProvider>
</StrictMode>,
rootElement
)
The provider itself is useless if it doesn’t provide any value, or values, to the app. In order to fix this, you need two things. First, you need some value, some data, you want to be available through the provider. Second, you have to make this data accessible from the provider.
The first can be fixed by creating new local state inside the provider. The useState hook will be perfect for this. The value of this state will be what you want to share across the app. Since useState hook also creates an update function, this will also give you a way to update this shared state.
To fix the second thing, you have to add value
attribute to the myContext.Provider
component returned by the NewProvider
component. The value of this attribute can be anything from a primitive data type to an object. If you want to share a single value, the first will be sufficient.
If you want to share multiple values, or values and functions, it will be better to use an object. It is nice to make values available across the app. Even better is to also allow changing these values across the app. So, let’s go with the object. Let’s create new state and expose both, the state and its update function via the provider.
// context.jsx
// Import createContext() method and useState hook from React:
import { createContext, useState } from 'react'
// Create new context:
export const newContext = createContext()
// Create new provider component:
export const NewProvider = (props) => {
// Create local state:
const [state, setState] = useState('')
// Prepare values to share:
const val = {
state, // The state itself
setState // The state update function
}
return (
{/* Set "val" as the value for "value" attribute: */}
<newContext.Provider value={value}>
{props.children}
</newContext.Provider>
)
}
You are almost done. You have context, you have provider and you have something to share via the provider. You have also wrapped the app with the provider and exposed some value via the Provider’s value
attribute. You can now access the state and setState function exposed via the provider anywhere in the app.
To achieve this, you need just two things. The first thing is the React useContext hook. The second thing is the exported context, the one you created in the beginning with the createContext()
method. When you combine these two you will have immediate access to state
and setState
you created in NewProvider
component.
Let’s create the main App
component. You saw this component in the index.jsx
file as the direct child of the provider (Creating the context provider section). This component will be simple. It will contain two components: heading showing welcome message and current value of state
and input to update the state
via setState
.
You will get both, state
and setState
, from the newContext
context. Remember that this context is provided by the NewProvider
component. You will get those values by calling the React useContext hook and passing the newContext
context as an argument.
// Import useContext hook from React:
import { useContext } from 'react'
// Import newContext context:
import { newContext } from './context'
// Create the App component:
export default function App() {
// Access the state and setState values in newContext:
const { state, setState } = useContext(newContext)
return (
<div>
{/* Display the value of "state" */}
<h1>Hello {state}</h1>
<h2>Change name:</h2>
{/*
Use "setState" update function to update the current value
of "state" with the current value of input:
*/}
<input type="text" onChange={(e) => setState(e.target.value)} />
</div>
)
}
There is basically no limit to how many contexts, and providers, you can have in your React app. You can have as many as you want, as long as you remember to add each provider as a wrapper. For example, we can add additional context for email to this simple sample app. This will require new context and new Provider component.
First, let’s create new context for email. This will be almost a mirror copy of the context you already have. You will mostly change just the names.
// email-context.jsx
// Import createContext() method from React:
import { createContext, useState } from 'react'
// Create new context:
export const emailContext = createContext()
// Create new email provider component:
export const EmailProvider = (props) => {
// Create local state for email:
const [email, setEmail] = useState('')
// Prepare values for sharing:
const val = {
email,
setEmail,
}
// Render emailContext.Provider exposing "val" variable:
return (
<emailContext.Provider value={val}>
{/* Render children components: */}
{props.children}
</emailContext.Provider>
)
}
Next, you have to import the email context in the main file, where you render the App
to the root element. When you have multiple providers their order doesn’t really matter. Important thing that the app, or some component where you want to use data from those providers, is wrapped with those providers.
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import { NewProvider } from './context'
// Import new email provider:
import { EmailProvider } from './email-context'
import App from './App'
const rootElement = document.getElementById('root')
ReactDOM.render(
<StrictMode>
{/* Add email provider as another wrapper of the App component: */}
<EmailProvider>
<NewProvider>
<App />
</NewProvider>
</EmailProvider>
</StrictMode>,
rootElement
)
With that, you can now use the React useContext hook with emailContext
to access the email
and setEmail
anywhere in the app.
import { useContext } from 'react'
import { newContext } from './context'
// Import new email context:
import { emailContext } from './email-context'
export default function App() {
const { state, setState } = useContext(newContext)
// Access the email and setEmail values in emailContext:
const { email, setEmail } = useContext(emailContext)
return (
<div>
{/* Render the value of "email": */}
<h1>
Hello {state}, {email}
</h1>
<h2>Change name:</h2>
<input type="text" onChange={(e) => setState(e.target.value)} />
<h2>Change email:</h2>
{/*
Allow to to update the current value of "email"
via the "setEmail" update function and text input:
*/}
<input type="text" onChange={(e) => setEmail(e.target.value)} />
</div>
)
}
Creating global states with React context is very easy. With the help of React useContext hook it is also easy to access these contexts and their data. I hope that this tutorial helped you understand how to create contexts and their providers and how to use useContext hook to communicate with them.
Original article source at https://blog.alexdevero.com
#react #hook #javascript
1644899922
Les composants fonctionnels n'ont pas toujours été la méthode préférée pour déclarer des composants dans React.
Avant l'introduction de la version 16.8 de React, les composants fonctionnels étaient traités comme des citoyens de seconde classe. Ils ne pouvaient pas gérer l'état, la logique et de nombreuses autres fonctionnalités de React, et nous ne les utilisions que pour rendre des composants très simples à l'interface utilisateur.
La version 16.8 de React a résolu ces problèmes en introduisant React Hooks, qui permet aux développeurs d'utiliser ces fonctionnalités de réaction dans des composants fonctionnels.
Dans cet article, vous apprendrez :
Les crochets sont des fonctions React intégrées introduites dans React version 16.8. Ils vous permettent d'utiliser des fonctionnalités de la bibliothèque React telles que les méthodes de cycle de vie, l'état et le contexte dans des composants fonctionnels sans avoir à vous soucier de la réécrire dans une classe.
Chaque nom React Hook est précédé du mot "use"
. Par exemple, useState
ou useEffect
. Ce format a été choisi car les Hooks permettent aux développeurs d'utiliser les fonctionnalités spéciales de la bibliothèque React. Vous utilisez donc use
cette fonctionnalité spéciale de la bibliothèque React.
De nombreux développeurs sont sceptiques quant à l'apprentissage de React Hooks. Mais vous ne devriez pas l'être. Voici quelques raisons pour lesquelles vous devriez commencer à utiliser React Hooks :
Les cours sont un obstacle à l'apprentissage de React correctement. Pour les utiliser, vous devez comprendre le fonctionnement du mot- this
clé. Vous devez également vous rappeler constamment de lier les gestionnaires d'événements, ainsi que d'autres méthodes redondantes rencontrées lorsque vous travaillez avec des classes dans React.
Les composants de classe sont généralement volumineux et essaient d'effectuer de nombreuses opérations. À la longue, ils deviennent difficiles à comprendre.
Les crochets résolvent ce problème en vous permettant de séparer les grands composants en diverses fonctions plus petites, plutôt que d'avoir à forcer toute la logique en un seul composant.
Les composants de classe sont livrés avec beaucoup de code passe-partout. Considérez le composant de compteur ci-dessous :
class Counter extends Component {
constructor(props) {
super(props)
this.state = {
count: 1,
}
}
render() {
return (
<div>
The Current Count: {this.state.count}
<div>
<button onClick={this.setState({ count: this.state.count - 1 })}>
add
</button>
<button onClick={this.setState({ count: this.state.count + 1 })}>
subtract
</button>
</div>
</div>
);
}
}
Voici un code équivalent utilisant un composant fonctionnel et React Hooks :
function Counter () {
const [count, setCount] = useState(1);
return (
<div>
The Current Count: {this.state.count}
<div>
<button onClick={() => setCount(count + 1)}>add</button>
<button onClick={() => setCount(count - 1)}>subtract</button>
</div>
</div>
);
};
Remarquez comment le composant de classe est beaucoup plus complexe. Vous avez besoin d'une classe pour étendre React, d'un constructeur pour initialiser l'état, et vous devez référencer le mot- this
clé partout.
L'utilisation de composants fonctionnels supprime une grande partie de cela, de sorte que notre code devient plus court et plus facile à lire et à entretenir.
Lors de l'utilisation de React Hooks, il y a quelques règles à respecter :
À ce jour, React a 10 crochets intégrés. Regardons les quatre plus courants :
useState
useEffect
useContext
useReducer
Le crochet useState vous permet de créer, mettre à jour et manipuler l'état à l'intérieur des composants fonctionnels.
React a ce concept d'état, qui sont des variables qui contiennent des données dont dépendent nos composants et qui peuvent changer avec le temps. Chaque fois que ces variables changent, React met à jour l'interface utilisateur en restituant le composant dans le DOM avec les valeurs actuelles des variables d'état.
Le crochet prend un seul argument optionnel : une valeur initiale pour l'état. Ensuite, il renvoie un tableau de deux valeurs :
Prenons l'exemple d'un composant de compteur :
Pour utiliser un Hook, la première étape consiste à importer le Hook en haut du fichier :
import { useState } from "react";
Ensuite, initialisez le Hook avec une valeur. En raison du fait qu'il renvoie un tableau, vous pouvez utiliser la déstructuration du tableau pour accéder aux éléments individuels du tableau, comme ceci :
const [count, setCount] = useState(0);
Avec cela, le code du composant sera :
import { useState } from "react";
function Counter() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
Current Cart Count: {count}
<div>
<button onClick={() => setCount(count - 1)}>Add to cart</button>
<button onClick={() => setCount(count + 1)}>Remove from cart</button>
</div>
</div>
);
}
Voici à quoi ressemblera le composant une fois rendu.
En cliquant sur le bouton Ajouter au panier ou Supprimer du panier , la valeur du nombre de variables d'état changera et le composant sera restitué avec la valeur mise à jour de l'état.
Si vous connaissez les méthodes de cycle de vie de la classe React, vous pouvez considérer le useEffect
crochet comme les méthodes de cycle de vie componentDidMount
, componentDidUpdate
et componentWillUnmount
, toutes combinées en une seule fonction. Il vous permet de répliquer les méthodes de cycle de vie de React dans des composants fonctionnels.
Le useEffect
crochet vous permet d'effectuer des effets secondaires dans les composants de fonction. Les effets secondaires sont des actions qui peuvent s'exécuter parallèlement aux opérations principales d'un composant, telles que les interactions d'API externes, la modification de variables d'état et la récupération de données.
Le useEffect
hook accepte 2 arguments :
Voici un exemple d'utilisation du Hook :
import { useState, useEffect } from "react";
function Counter() {
// Declare state variables
const [count, setCount] = useState(0);
const [product, setProduct] = useState("Eggs");
useEffect(() => {
console.log(`${product} will rule the world!`);
});
return (
<div>
Current {product}'s count: {count}
<div>
<button onClick={() => setCount(count + 1)}>Add to cart</button>
<button onClick={() => setCount(count - 1)}>Remove from cart</button>
Change Product:{" "}
<input type="text" onChange={(e) => setProduct(e.target.value)} />
</div>
</div>
);
}
Dans l'exemple, l'effet s'exécutera après chaque mise à jour d'état.
Pour exécuter le crochet uniquement lorsque certaines valeurs ont changé, transmettez les variables en tant que dépendance dans le tableau :
useEffect(() => {
console.log(`${product} will rule the world!`);
}, [product]); // Only re-run the effect if the value of product changes
Avec ce changement, le crochet ne s'exécutera que lors du premier rendu et lorsque la valeur du produit est modifiée.
Si vous voulez qu'un effet ne s'exécute qu'une seule fois lors du premier rendu, comme faire des appels d'API lorsque le composant est rendu pour la première fois, vous pouvez passer un tableau vide comme dépendance comme ceci :
useEffect(() => {
console.log("This runs once on first render");
}, []);
En fournissant un tableau vide, il indique au crochet d'écouter les changements d'état zéro, de sorte qu'il ne s'exécutera qu'une seule fois.
Le useContext
Hook fonctionne avec l'API React Context. Il vous permet de rendre des données particulières accessibles à tous les composants de l'application, quelle que soit leur profondeur d'imbrication.
React a un flux de données unidirectionnel, où les données ne peuvent être transmises que du parent à l'enfant. Pour transmettre des données (comme l'état) d'un parent à un composant enfant, vous devrez les transmettre manuellement en tant qu'accessoire à différents niveaux en fonction de la profondeur d'imbrication du composant enfant.
Pour des données telles que la langue préférée de l'utilisateur, le thème ou les propriétés de l'utilisateur authentifié, il est fastidieux de devoir les transmettre manuellement dans l'arborescence des composants.
L'API Context de React et le useContext
Hook facilitent la transmission de données entre tous les composants de l'application.
Il accepte un objet de contexte créé à l'aide de React.createContext
et renvoie le contexte actuel comme suit :
const value = useContext(SomeContext);
Regardons un exemple du fonctionnement du Hook :
Tout d'abord, créez un contexte pour utiliser le Hook. Par exemple, voici un UserContext pour obtenir la valeur des utilisateurs actuels :
import React from "react";
// some mock context values
const users = [
{
name: "Harry Potter",
occupation: "Wizard",
},
{
name: "Kent Clark",
occupation: "Super hero",
},
];
export const UserContext = React.createContext(users);
Chaque contexte a un wrapper de fournisseur, qui permet à ses composants enfants de s'abonner aux modifications du contexte et transmet la valeur du contexte via une prop de valeur.
Si la prop de valeur du fournisseur est mise à jour, ses composants enfants consommateurs seront restitués avec la nouvelle valeur de contexte.
function Users() {
return (
<UserContext.Provider value={users}>
<UserProfile />
</UserContext.Provider>
);
}
Dans l'exemple, UserProfile
on fait de la composante consommatrice du contexte.
import React, { useContext } from "react";
import { UserContext } from "./App";
export function UserProfile() {
const users = useContext(UserContext);
return (
<div>
{users.map((user) => (
<li>
I am {user.name} and I am a {user.occupation}!
</li>
))}
</div>
);
}
Cela affichera les propriétés des utilisateurs actuels :
Le useReducer
Crochet est une alternative au useState
Crochet. La différence est qu'il permet une logique plus complexe et des mises à jour d'état qui impliquent plusieurs sous-valeurs.
Semblable à useState
, useReducer
vous permet de créer des variables de type état qui entraînent la mise à jour de l'interface utilisateur chaque fois qu'elles changent.
Ce Hook accepte 2 arguments : une fonction réductrice et un état initial.
useReducer(reducer, initialState);
Il renvoie un tableau de deux valeurs qui peuvent être déstructurées à la valeur actuelle de l'état et une fonction de répartition.
const [state, dispatch] = useReducer(reducer, initialState);
Découvrons ses arguments et les valeurs renvoyées :
En règle générale, nous parcourons le type d'actions que nous avons effectuées dans notre application via une instruction switch pour déterminer comment la valeur de l'état va changer. C'est ainsi que le Hook met à jour les valeurs de son état.
function reducer(state, action) {
switch (action.type) {
case "CASE_1":
return {
updatedState,
};
case "CASE_2":
return {
updatedState,
};
default:
return state;
}
}
La fonction dispatch distribue généralement un objet au format :
dispatch({ type: "ACTION_TYPE", payload: optionalArguments });
Où type est la description de l'action et la charge utile est les arguments que vous souhaitez transmettre au réducteur.
Un crochet personnalisé est l'idée d'extraire la logique de composant couramment utilisée de l'interface utilisateur dans des fonctions JavaScript en utilisant les crochets React déjà disponibles. Cela vous aide à éviter la duplication de code et vous permet de rendre cette logique réutilisable dans plusieurs composants.
Examinons un exemple de crochet personnalisé qui renverra une réponse à partir de toute URL d'API valide que nous lui transmettrons.
//useFetch.js
import { useState, useEffect } from "react";
export function useFetch(url) {
//values
const [data, setData] = useState(null);
const [error, setError] = useState("");
useEffect(() => {
fetch(url)
.then(res => {
if (!res.ok) {
throw Error("something wrong, çould not connect to resource");
}
setData(res.json());
})
.then(() => {
setError("");
})
.catch( error => {
console.warn(`sorry an error occurred, due to ${error.message} `);
setData(null);
setError(error.message);
});
}, [url]);
return [data, error];
}
Vous pouvez désormais utiliser cette logique n'importe où dans votre application simplement en important la fonction et en passant un chemin d'API comme argument, plutôt que de tout écrire à partir de zéro.
J'espère que vous avez pu voir à quel point React Hooks est utile. Ils vous permettent de créer des composants efficaces à la volée sans vous soucier des tracas liés aux composants de classe.
Qu'il s'agisse de vous permettre de vous concentrer sur l'écriture de votre code principal ou de vous permettre de créer vos propres crochets personnalisés... Les crochets React sont tellement cool ! Je suis ravi que vous les essayiez par vous-même.
Si vous avez trouvé cet article utile, partagez-le avec vos amis et votre réseau. Aussi, n'hésitez pas à vous connecter avec moi sur Twitter et mon blog où je partage un large éventail d'articles et de ressources pédagogiques gratuits.
Merci d'avoir lu et bon codage !
Link: https://www.freecodecamp.org/news/the-beginners-guide-to-react-hooks/
1643085923
It's easier to write code that makes the useEffect hook go off the rails than it is to write code that uses it the right way. Let's develop some strategies that you can use to make sure the useEffects you write always work when and how you expect them to work.
0:00 Introduction
0:27 Setup
1:38 Creating useFetch
6:42 [ DANGER ZONE ] Depending On Objects
7:30 All About References
13:17 Fixing Our Dependencies
15:16[ DANGER ZONE ] Function Dependencies
19:02 Quick Check-In
19:50 Cleanup Functions
21:25 Depending On State Mutated In The useEffect
24:51 Outroduction
Code: https://github.com/jherr/taming-useeffect
#react #hook