1668761897
Let's learn React pure component and how to implement one.
By default, React will always re-render the component each time there is a change in state or props value. A Pure Component is a component that performs a check on the value of React props before deciding whether to re-render the component or not. Let me show you by an example.
Imagine you have an application that displays a movie title and release status:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "Titanic",
isReleased: true
};
}
toogleRelease = () => {
console.log("toogleRelease");
this.setState((prevState) => ({
isReleased: !prevState.isReleased
}));
};
render() {
const { title, isReleased } = this.state;
return (
<>
<MovieTitle title={title} />
<h1>Movie is released: {isReleased ? "yes" : "no"} </h1>
<button onClick={this.toogleRelease}>Toogle Release</button>
</>
);
}
}
Next, you have a MovieTitle
component with the following content:
class MovieTitle extends React.Component {
render() {
console.log("MovieTitle is rendered");
return <h1>Movie title {this.props.title} </h1>;
}
}
Here’s a running demo.
When you change the isReleased
value by click on the button, you’ll find that MovieTitle
gets re-rendered because React will print the log to the console. But actually, MovieTitle
component doesn’t need to re-render because even if the value of isReleased
is changed, its output remains the same.
To prevent the re-rendering and optimize your React app performance, you can extends
the PureComponent
class instead of Component
:
class MovieTitle extends React.PureComponent {
}
Now when you click on toogle release, you won’t see React printing the log because MovieTitle
will check the props and determine whether to render or not.
You can do the same with function-based components, but instead of extending the PureComponent
class, you need to wrap the function component with React.memo()
, which memoize (cache) the output of the component.
The function will return a memoized component that you can call from other components:
function MovieTitle(){
}
const MemoizedMovie = React.memo(MovieTitle)
Now you just need to call the memoized MovieTitle
inside App
component:
render() {
return (
<MemoizedMovie title={title} />
);
}
React’s “pure component” is a performance optimization method which implements the shouldComponentUpdate
method underneath, but you should only use it sparingly, or even none at all.
This is because the performance optimization gained from using pure components are very trivial. React already re-render components very fast (as in less than 100 milliseconds fast, mostly around 20ms in small applications)
When you use pure components, comparing states and props will consume memory storage.
There will be a little delay because React has to compare the states and props before deciding whether to render or not. When React doesn’t re-render the component, you will gain a few milliseconds. When React does re-render, you will be slowed by a few milliseconds. Most people won’t be able to tell the difference anyway.
Original article source at: https://sebhastian.com/
1668745159
Learn shouldComponentUpdate and when you should use it
During React component’s lifecycle, there’s a set of specific methods that gets executed during a certain moment. These methods are called lifecycle methods, and shouldComponentUpdate
is a part of this method group.
This method is used to let your component exit the update lifecycle and prevent re-rendering of the component if there’s no meaningful change. The method will receive two arguments: the next props and state that are being received. You can then compare these data to the current data.
Here’s an implementation example:
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
The method is run right after React internal state is updated but before the component re-renders and the update is reflected in the browser:
By default, this method is not implemented into React, and the most optimum implementation of this method can be found in React’s PureComponent API.
Through my experience in creating React applications, I rarely encounter a case where the use of componentShouldUpdate
is justified. The reason is that React rendering execution is already fast by default. If you check on React’s performance. Most component render would take about 1 to 2 milliseconds. On a very complicated component with lots of computation, it might take React 10 milliseconds.
Let’s say you implemented shouldComponentUpdate
manually and you reduced React’s render time from 10 milliseconds to 1 milliseconds. You just created a 10x improvement, sure. But I bet you won’t feel any different at all. That’s because we’re humans, and we won’t be able to discern a significant improvement in load speed between 10ms and 1 ms.
In general, this method shouldn’t be used at all. Or if you really need it, you should implement Pure Component API so React can maintain it for you.
Original article source at: https://sebhastian.com/react-shouldcomponentupdate/
1667386920
The ErrorHandler component provides tools to manage errors and ease debugging PHP code.
$ composer require symfony/error-handler
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\ErrorHandler\DebugClassLoader;
Debug::enable();
// or enable only one feature
//ErrorHandler::register();
//DebugClassLoader::enable();
// If you want a custom generic template when debug is not enabled
// HtmlErrorRenderer::setTemplate('/path/to/custom/error.html.php');
$data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) {
// if any code executed inside this anonymous function fails, a PHP exception
// will be thrown, even if the code uses the '@' PHP silence operator
$data = json_decode(file_get_contents($filename), true);
$data['read_at'] = date($datetimeFormat);
file_put_contents($filename, json_encode($data));
return $data;
});
Author: Symfony
Source Code: https://github.com/symfony/error-handler
License: MIT license
1667383140
This project backports features found in the latest PHP versions and provides compatibility layers for some extensions and functions. It is intended to be used when portability across PHP versions and extensions is desired.
Polyfills are provided for:
apcu
extension when the legacy apc
extension is installed;ctype
extension when PHP is compiled without ctype;mbstring
and iconv
extensions;uuid
extension;MessageFormatter
class and the msgfmt_format_message
functions;Normalizer
class and the grapheme_*
functions;utf8_encode
and utf8_decode
functions from the xml
extension or PHP-7.2 core;Collator
, NumberFormatter
, Locale
and IntlDateFormatter
classes, limited to the "en" locale;intl_error_name
, intl_get_error_code
, intl_get_error_message
and intl_is_failure
functions;idn_to_ascii
and idn_to_utf8
functions;hex2bin
function, the CallbackFilterIterator
, RecursiveCallbackFilterIterator
and SessionHandlerInterface
classes introduced in PHP 5.4;array_column
, boolval
, json_last_error_msg
and hash_pbkdf2
functions introduced in PHP 5.5;password_hash
and password_*
related functions introduced in PHP 5.5, provided by the ircmaxell/password-compat
package;hash_equals
and ldap_escape
functions introduced in PHP 5.6;*Error
classes, the error_clear_last
, preg_replace_callback_array
and intdiv
functions introduced in PHP 7.0;random_bytes
and random_int
functions introduced in PHP 7.0, provided by the paragonie/random_compat
package;PHP_INT_MIN
constant introduced in PHP 7.0,SessionUpdateTimestampHandlerInterface
interface introduced in PHP 7.0,is_iterable
function introduced in PHP 7.1;Binary
utility class to be used when compatibility with mbstring.func_overload
is required;spl_object_id
and stream_isatty
functions introduced in PHP 7.2;mb_ord
, mb_chr
and mb_scrub
functions introduced in PHP 7.2 from the mbstring
extensionsapi_windows_vt100_support
function (Windows only) introduced in PHP 7.2;PHP_FLOAT_*
constant introduced in PHP 7.2;PHP_OS_FAMILY
constant introduced in PHP 7.2;is_countable
function introduced in PHP 7.3;array_key_first
and array_key_last
functions introduced in PHP 7.3;hrtime
function introduced in PHP 7.3;JsonException
class introduced in PHP 7.3;get_mangled_object_vars
, mb_str_split
and password_algos
functions introduced in PHP 7.4;fdiv
function introduced in PHP 8.0;get_debug_type
function introduced in PHP 8.0;preg_last_error_msg
function introduced in PHP 8.0;str_contains
function introduced in PHP 8.0;str_starts_with
and str_ends_with
functions introduced in PHP 8.0;ValueError
class introduced in PHP 8.0;UnhandledMatchError
class introduced in PHP 8.0;FILTER_VALIDATE_BOOL
constant introduced in PHP 8.0;get_resource_id
function introduced in PHP 8.0;Attribute
class introduced in PHP 8.0;Stringable
interface introduced in PHP 8.0;PhpToken
class introduced in PHP 8.0 when the tokenizer extension is enabled;array_is_list
function introduced in PHP 8.1;enum_exists
function introduced in PHP 8.1;MYSQLI_REFRESH_REPLICA
constant introduced in PHP 8.1;ReturnTypeWillChange
attribute introduced in PHP 8.1;AllowDynamicProperties
attribute introduced in PHP 8.2;SensitiveParameter
attribute introduced in PHP 8.2;SensitiveParameterValue
class introduced in PHP 8.2;It is strongly recommended to upgrade your PHP version and/or install the missing extensions whenever possible. This polyfill should be used only when there is no better choice or when portability is a requirement.
Compatibility notes
To write portable code between PHP5 and PHP7, some care must be taken:
\*Error
exceptions must be caught before \Exception
;error_clear_last()
, the result of $e = error_get_last()
must be verified using isset($e['message'][0])
instead of null !== $e
.Usage
When using Composer to manage your dependencies, you should not require
the symfony/polyfill
package, but the standalone ones:
symfony/polyfill-apcu
for using the apcu_*
functions,symfony/polyfill-ctype
for using the ctype functions,symfony/polyfill-php54
for using the PHP 5.4 functions,symfony/polyfill-php55
for using the PHP 5.5 functions,symfony/polyfill-php56
for using the PHP 5.6 functions,symfony/polyfill-php70
for using the PHP 7.0 functions,symfony/polyfill-php71
for using the PHP 7.1 functions,symfony/polyfill-php72
for using the PHP 7.2 functions,symfony/polyfill-php73
for using the PHP 7.3 functions,symfony/polyfill-php74
for using the PHP 7.4 functions,symfony/polyfill-php80
for using the PHP 8.0 functions,symfony/polyfill-php81
for using the PHP 8.1 functions,symfony/polyfill-php82
for using the PHP 8.2 functions,symfony/polyfill-iconv
for using the iconv functions,symfony/polyfill-intl-grapheme
for using the grapheme_*
functions,symfony/polyfill-intl-idn
for using the idn_to_ascii
and idn_to_utf8
functions,symfony/polyfill-intl-icu
for using the intl functions and classes,symfony/polyfill-intl-messageformatter
for using the intl messageformatter,symfony/polyfill-intl-normalizer
for using the intl normalizer,symfony/polyfill-mbstring
for using the mbstring functions,symfony/polyfill-util
for using the polyfill utility helpers.symfony/polyfill-uuid
for using the uuid_*
functions,Requiring symfony/polyfill
directly would prevent Composer from sharing correctly polyfills in dependency graphs. As such, it would likely install more code than required.
Design
This package is designed for low overhead and high quality polyfilling.
It adds only a few lightweight require
statements to the bootstrap process to support all polyfills. Implementations are then loaded on-demand when needed during code execution.
If your project requires a minimum PHP version it is advisable to add polyfills for lower PHP versions to the replace
section of your composer.json
. This removes any overhead from these polyfills as they are no longer part of your project. The same can be done for polyfills for extensions that you require.
If your project requires php 7.0, and needs the mb extension, the replace section would look something like this:
{
"replace": {
"symfony/polyfill-php54": "*",
"symfony/polyfill-php55": "*",
"symfony/polyfill-php56": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-mbstring": "*"
}
}
Polyfills are unit-tested alongside their native implementation so that feature and behavior parity can be proven and enforced in the long run.
Author: Symfony
Source Code: https://github.com/symfony/polyfill
License: MIT license
1667247900
The Stopwatch component provides a way to profile code.
$ composer require symfony/stopwatch
use Symfony\Component\Stopwatch\Stopwatch;
$stopwatch = new Stopwatch();
// optionally group events into sections (e.g. phases of the execution)
$stopwatch->openSection();
// starts event named 'eventName'
$stopwatch->start('eventName');
// ... run your code here
// optionally, start a new "lap" time
$stopwatch->lap('foo');
// ... run your code here
$event = $stopwatch->stop('eventName');
$stopwatch->stopSection('phase_1');
Author: Symfony
Source Code: https://github.com/symfony/stopwatch
License: MIT license
1666830840
Just add the Source folder to your project.
or use CocoaPods with Podfile:
pod 'Navigation-stack'
or Carthage users can simply add to their Cartfile
:
github "Ramotion/navigation-stack"
YourNavigationController inherit from NavigationStack
add code to root viewViewController
override func viewDidLoad() {
super.viewDidLoad()
navigationController!.interactivePopGestureRecognizer?.delegate = self
}
extension YourViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if navigationController?.viewControllers.count == 2 {
return true
}
if let navigationController = self.navigationController as? NavigationStack {
navigationController.showControllers()
}
return false
}
}
Author: Ramotion
Source Code: https://github.com/Ramotion/navigation-stack
License: MIT license
1666519560
Installation
Just add the RAMPaperSwitch
folder to your project.
or use CocoaPods with Podfile:
pod 'RAMPaperSwitch'
or Carthage users can simply add to their Cartfile
:
github "Ramotion/paper-switch"
Usage
RAMPaperSwitch is a drop-in replacement of UISwitch. You just need to set the onTintColor
property of the switch, and it will automatically paint over its superview with the selected color. You have ability to set duration of animation instead of default value.
Create a new UISwitch in your storyboard or nib.
Set the class of the UISwitch to RAMPaperSwitch in your Storyboard or nib.
Set onTintColor
for the switch
Set duration
property programmatically if You want to change animation duration.
Add animation for other views near the switch if need.
Animate views
You can animate other views near the switch. For example, you can change color to views or labels that are inside the same superview. Duration of animation can be gotten from the RAMPaperSwitch's property duration
. You can animate CoreAnimation properties like this:
self.paperSwitch.animationDidStartClosure = {(onAnimation: Bool) in
UIView.transitionWithView(self.label, duration: self.paperSwitch.duration, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
self.label.textColor = onAnimation ? UIColor.whiteColor() : UIColor.blueColor()
}, completion:nil)
}
Try this UI component and more like this in our iOS app. Contact us if interested.
Author: Ramotion
Source Code: https://github.com/Ramotion/paper-switch
License: MIT license
1666430520
Just add CircleMenuLib folder to your project.
or use CocoaPods with Podfile:
pod 'CircleMenu'
or Carthage users can simply add to their Cartfile
:
github "Ramotion/circle-menu"
with storyboard
Create a new UIButton inheriting from CircleMenu
Add images for Normal and Selected state
Use delegate method to configure buttons
func circleMenu(circleMenu: CircleMenu, willDisplay button: UIButton, atIndex: Int)
@IBInspectable var buttonsCount: Int = 3
@IBInspectable var duration: Double = 2 // circle animation duration
@IBInspectable var distance: Float = 100 // distance between center button and buttons
programmatically
let button = CircleMenu(
frame: CGRect(x: 200, y: 200, width: 50, height: 50),
normalIcon:"icon_menu",
selectedIcon:"icon_close",
buttonsCount: 4,
duration: 4,
distance: 120)
button.delegate = self
button.layer.cornerRadius = button.frame.size.width / 2.0
view.addSubview(button)
delegate methods
// configure buttons
optional func circleMenu(circleMenu: CircleMenu, willDisplay button: UIButton, atIndex: Int)
// call before animation
optional func circleMenu(circleMenu: CircleMenu, buttonWillSelected button: UIButton, atIndex: Int)
// call after animation
optional func circleMenu(circleMenu: CircleMenu, buttonDidSelected button: UIButton, atIndex: Int)
// call upon cancel of the menu - fires immediately on button press
optional func menuCollapsed(circleMenu: CircleMenu)
// call upon opening of the menu - fires immediately on button press
optional func menuOpened(circleMenu: CircleMenu)
Try this UI component and more like this in our iOS app. Contact us if interested.
Author: Ramotion
Source Code: https://github.com/Ramotion/circle-menu
License: MIT license
1666080984
Just add the Source folder to your project.
or use CocoaPods with Podfile:
pod 'expanding-collection'
or Carthage users can simply add to their Cartfile
:
github "Ramotion/expanding-collection"
import expanding_collection
Create UICollectionViewCell inherit from BasePageCollectionCell
(recommend create cell with xib file)
Adding FrontView
@IBOutlet weak var frontContainerView: UIView!
@IBOutlet weak var frontConstraintY: NSLayoutConstraint!
Adding BackView
@IBOutlet weak var backContainerView: UIView!
, @IBOutlet weak var backConstraintY: NSLayoutConstraint!
)Cell example DemoCell
If set tag = 101
for any FrontView.subviews
this view will be hidden during the transition animation
Create a UIViewController inheriting from ExpandingViewController
Register Cell and set Cell size:
override func viewDidLoad() {
itemSize = CGSize(width: 214, height: 460) //IMPORTANT!!! Height of open state cell
super.viewDidLoad()
// register cell
let nib = UINib(nibName: "NibName", bundle: nil)
collectionView?.registerNib(nib, forCellWithReuseIdentifier: "CellIdentifier")
}
Add UICollectionViewDataSource methods
extension YourViewController {
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CellIdentifier"), forIndexPath: indexPath)
// configure cell
return cell
}
}
Open Cell animation
override func viewDidLoad() {
itemSize = CGSize(width: 214, height: 264)
super.viewDidLoad()
// register cell
let nib = UINib(nibName: "CellIdentifier", bundle: nil)
collectionView?.registerNib(nib, forCellWithReuseIdentifier: String(DemoCollectionViewCell))
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
cell.cellIsOpen(!cell.isOpened)
}
if you use this delegates method:
func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath)
func scrollViewDidEndDecelerating(scrollView: UIScrollView)
must call super method:
func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
super.collectionView(collectionView: collectionView, willDisplayCell cell: cell, forItemAtIndexPath indexPath: indexPath)
// code
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
super.scrollViewDidEndDecelerating(scrollView: scrollView)
// code
}
Create a UITableViewController inheriting from ExpandingTableViewController
Set header height default 236
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
headerHeight = ***
}
OR
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
headerHeight = ***
}
Call the push method in YourViewController to YourTableViewController
if cell.isOpened == true {
let vc: YourTableViewController = // ... create view controller
pushToViewController(vc)
}
For back transition use popTransitionAnimation()
Author: Ramotion
Source Code: https://github.com/Ramotion/expanding-collection
License: MIT license
1664431680
Probably the most complete selecting solution for Vue.js 2.0, without jQuery.
For Vue 3.0 compatible version see next
branch.
:key
props has changed to :track-by
, due to conflicts with Vue 2.0.v-model
@update
has changed to @input
to also work with v-model:selected
has changed to :value
for the same reason.vue
files, please add vueify
transform.npm install vue-multiselect
<template>
<div>
<multiselect
v-model="selected"
:options="options">
</multiselect>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
components: { Multiselect },
data () {
return {
selected: null,
options: ['list', 'of', 'options']
}
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
Example JSFiddle – Use this for issue reproduction.
in jade-lang/pug-lang
multiselect(
:value="value",
:options="source",
:searchable="false",
:close-on-select="false",
:allow-empty="false",
@input="updateSelected",
label="name",
placeholder="Select one",
track-by="name"
)
multiselect(
v-model="value",
:options="source",
:close-on-select="true",
:clear-on-select="false",
placeholder="Select one",
label="name",
track-by="name"
)
multiselect(
v-model="multiValue",
:options="source",
:multiple="true",
:close-on-select="true",
placeholder="Pick some",
label="name",
track-by="name"
)
with @tag
event
multiselect(
v-model="taggingSelected",
:options="taggingOptions",
:multiple="true",
:taggable="true",
@tag="addTag",
tag-placeholder="Add this as new tag",
placeholder="Type to search or add tag",
label="name",
track-by="code"
)
addTag (newTag) {
const tag = {
name: newTag,
code: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))
}
this.taggingOptions.push(tag)
this.taggingSelected.push(tag)
},
multiselect(
v-model="selectedCountries",
:options="countries",
:multiple="multiple",
:searchable="searchable",
@search-change="asyncFind",
placeholder="Type to search",
label="name"
track-by="code"
)
span(slot="noResult").
Oops! No elements found. Consider changing the search query.
methods: {
asyncFind (query) {
this.countries = findService(query)
}
}
# serve with hot reload at localhost:8080
npm run dev
# distribution build with minification
npm run bundle
# build the documentation into docs
npm run docs
# run unit tests
npm run test
# run unit tests watch
npm run unit
For detailed explanation on how things work, checkout the guide and docs for vue-loader.
Visit: vue-multiselect.js.org
Author: Shentao
Source Code: https://github.com/shentao/vue-multiselect
License: MIT license
1661458980
How much time do you spend copying and pasting the component folder to create a new one ?
This is a tool to generate different types of React components from the terminal.
What you can do with this tool ?
$ npm install -g create-component-app
$ cd ~/my-projects
$ create-component-app
connect
function of reduxjs
jsx
css
scss
sass
less
path
of the new componentCreate-component-app uses cosmiconfig for configuration file support. This means you can configure cca via:
.ccarc
file, written in YAML or JSON, with optional extensions: .yaml/.yml/.json
.cca.config.js
file that exports an object."cca"
key in your package.json
file.The configuration file will be resolved starting from the root of your project, and searching up the file tree until a config file is (or isn't) found.
An example configuration file can be found here: .ccarc.example, you can use this file by copying it to the root of your project.
Currently supported options are:
Option | Description |
---|---|
type | Default type of the component ["stateless", "class", "pure"] |
templatesDirPath | Default path to get the templates from the custom templates folder |
path | Default path to create component file and folder |
jsExtension | Default extension for your javascript file ["js", "jsx"] |
cssExtension | Default extension for your css file ["css", "scss", "sass", "less", false] . Set to false if you don't want a style file |
includeTests | Default flag to include a test file in the folder [true, false] |
includeStories | Default flag to include a storybook file in the folder [true, false] |
indexFile | Default flag to create an index file in the folder [false, true] |
connected | Default flag to integrate connect redux in the index file [false, true] |
componentMethods | Only for "class" and "pure", insert method inside the component (i.e. ["componentDidMount", "shouldComponentUpdate", "onClick"] ). render and constructor will be always included. |
fileNames | Choose the specific filename for your component's file. (COMPONENT_NAME will be replaced) |
fileNames.testFileName | specify the file name of your test file |
fileNames.componentFileName | specify the component file name |
fileNames.styleFileName | specify the style file name !!IMPORTANT: Include cssExtension. |
config.json
:$ create-component-app --config path/to/your/config.json
Passing a config file via the CLI overrides the configuration file loaded by cosmiconfig
$ create-component-app --path path/destionation
Passing a param via the CLI overrides the configuration file loaded by cosmiconfig
Simple steps to create your own templates docs/custom-templates
Now, the first question that you receive is Do you wanna choose a template?
if you answer yes, you can see the list of templates from the community.
templatesDirPath
- a custom path to the user custom templates folder.templates
- a list of used templates (with a default) to filter the listcreate-component-app -t templateName
Now, the community can offer their templates! How?
Check the issue list to contribute on some activities or to advice new features! The library is open to everybody, contribute improve your skills.
create-component-app
is maintained under the Semantic Versioning guidelines.
Use npm run watch
while coding.
Author: CVarisco
Source Code: https://github.com/CVarisco/create-component-app
License: MIT license
1661436723
TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them.
TODO: List what your package can do. Maybe include images, gifs, or videos.
TODO: List prerequisites and provide or point to information on how to start using the package.
TODO: Include short and useful examples for package users. Add longer examples to /example
folder.
const like = 'sample';
TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more.
Run this command:
With Flutter:
$ flutter pub add friendly_component
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
friendly_component: ^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:friendly_component/friendly_component.dart';
example/lib/main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Author: Virakyuthpk
Source Code: https://github.com/virakyuthpk/friendly_component
License: MIT license
1655799420
今日のほとんどのフロントエンドアプリケーションには、ある種の検索ボックスが必要です。これは、ユーザーがページ上で最初に操作するコンポーネントである場合があります。たとえば、Airbnb、Uber、Googleマップなどです。動作するだけでなく、ユーザーが目的のタスクを完了するようにガイドするのに十分な機能を備えた検索コンポーネントを作成することは、アプリケーションのユーザーエクスペリエンスにとって不可欠です。
Turnstoneは、React開発者がまさにそれを実行できるようにする新しいライブラリです。この軽量ライブラリ(12.2kB Gzip)には、オートコンプリート、自動キャッシング、WAI-ARIAアクセシビリティ、および機能的でアクセス可能な検索コンポーネントを構築できるその他の機能が付属しています。
ターンストーン要素は、CSSモジュールやTailwindCSSなどのさまざまなCSSメソッドを使用して簡単にカスタマイズできます。この記事では、TurnstoneとTailwindを使用して、マーベルコミックAPIにクエリを実行し、マーベルシネマティックユニバースに属するキャラクターを検索するアプリケーションを作成します。
GitHubでライブプロジェクトとソースコードを表示します。
このチュートリアルに従うには、次のものが必要です。
Tailwind CSSの知識はありがたいですが、必須ではありません。
Marvel Developersサイトにアクセスして、新しいアカウントを登録してください。次に、公開APIキーと秘密APIキーが表示される[マイデベロッパーアカウント]に移動します。後で使用するために公開鍵をコピーします。
アプリがこのAPIにリクエストを送信する前に、そのドメイン名をリファラーサイトのリストに含める必要があります。同じページで、リファラーサイトのセクションまで下にスクロールし、アプリのドメイン名を追加しますlocalhost
。アスタリスク*
を使用してすべてのドメインからのリクエストを受け入れることもできますが、これは完全に安全というわけではありません。
これで、開発を開始する準備が整いました。
コンポーネントは、検索ボックスのTurnstone
さまざまな部分を制御するために使用されるさまざまなプロパティを受け入れます。コンポーネントのスタイル設定、検索ボックスのクエリ元のデータソース、エラーメッセージなど、すべてを適切なプロパティで構成できます。
このアプリケーションの構築に使用する重要なもののいくつかを見ていきましょう。
typeahead
タイプ:boolean
typeahead
—別名オートコンプリート—は、ユーザーが入力している単語の残りの部分をアプリケーションが予測する機能です。これはtrue
デフォルトで設定されています。
maxItems
タイプ:number
このプロパティは、に表示される検索結果の最大数を制御しますlistbox
。
listbox
次のように入力します:object
、、array
またはfunction
listbox
ユーザーのクエリに応じて結果をレンダリングする方法を指定します。このプロパティは、データのソースと検索タイプ(またはのいずれstartsWith
か)を制御しcontains
ます。
オブジェクトとして、次のlistbox
ように単一のデータソースをクエリします。
const listbox = {
displayField: 'characters',
data: (query) =>
fetch(`/api/characters?q=${query}`)
.then(response => response.json()),
searchType: 'startsWith',
}
return (
<Turnstone listbox={listbox} />
)
data
上記は、戻り値がアイテムの配列に解決されるPromiseである必要がある関数です。この関数は、現在のquery
文字列を引数として受け取り、文字列が変更されるたびに再実行debounceWait
されます。そのため、、という別の小道具が必要になります。
アレイとして使用listbox
する場合、複数のソースからデータを収集できます。
const listbox = [
{
id: 'cities',
name: 'Cities',
ratio: 8,
displayField: 'name',
data: (query) =>
fetch(`/api/cities?q=${encodeURIComponent(query)}`)
.then(response => response.json()),
searchType: 'startswith'
},
{
id: 'airports',
name: 'Airports',
ratio: 2,
displayField: 'name',
data: (query) =>
fetch(`/api/airports?q=${encodeURIComponent(query)}`)
.then(response => response.json()),
searchType: 'contains'
}
]
return (
<Turnstone listbox={listbox} />
)
このシナリオでは、プロパティを使用して、に関連しratio
て占める結果の数を指定できます。これは、たとえばが10に設定されている場合、各データソースからの数を合計して10にする必要があることを意味します。listboxmaxItemsmaxItemsratio
styles
タイプ:object
キーがTurnstoneによってレンダリングされた要素を表すオブジェクト。class
対応する各値は、要素の属性を表す文字列です。
const styles = {
input: 'w-full h-12 border border-slate-300 py-2 pl-10',
listbox: 'w-full bg-white sm:border sm:border-blue-300 sm:rounded text-left sm:mt-2 p-2 sm:drop-shadow-xl',
groupHeading: 'cursor-default mt-2 mb-0.5 px-1.5 uppercase text-sm text-rose-300',
}
return (
<Turnstone styles={styles} />
)
Tailwindがいかに簡単に適合し、スタイリングプロセスを容易にするかがわかります。ドキュメントで利用可能なTurnstone要素のリストを表示します。
debounceWait
タイプ:number
このプロパティは、ユーザーが入力を終了してからクエリが関数に送信されるまでの待機時間をミリ秒単位で指定しますfetch
。
defaultListbox
このプロパティは同じですlistbox
が、検索ボックスにフォーカスがある場合にクエリ文字列なしで表示されます。これは通常listbox
、最近の検索用にを作成するために使用されます。
const defaultListBox = {
displayField: 'Recent Searches',
data: () => Promise.resolve(JSON.parse(localStorage.getItem('recentSearches')) || [])
}
return (
<Turnstone defaultListBox={defaultListBox} />
)
ターミナルを開き、次のコマンドを使用して新しいReactアプリケーションを作成します。
npx create-react-app turnstone-demo
インストールが完了したら、プロジェクトのディレクトリに移動します。
cd turnstone-demo
そして、TurnstoneとTailwind CSSをインストールします—ピアの依存関係、PostCSS 、Autoprefixerと一緒に:
npm install -D turnstone tailwindcss postcss autoprefixer
APIキーの環境変数を作成することから始めましょう。プロジェクトのルートで、.env
ファイルを作成し、APIキーを保存します
// .env
REACT_APP_MARVEL_APIKEY = 'your_apikey_here'
REACT_APP_
Create React Appは、必要なプレフィックスで作成された環境変数のサポートを提供します。この変数は、アプリ内でとしてアクセスできますprocess.env.REACT_APP_MARVEL_APIKEY
。
注意 :公開リポジトリでキーを公開しないように、ファイルに追加すること.env
を忘れないでください。 .gitignore
プロジェクトのデモで見られる基になる画像の背景は、次のCSSクラスで作成されます。
// App.css
.image-backdrop {
background-image: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('../public/comic-backdrop.jpg');
height: 100vh;
width: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
body
このクラスをのタグに添付するpublic/index.html
と、検索ボックスを配置するための画像の背景が必要になります。
// public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<!-- markup -->
</head>
<body class="image-backdrop">
<!-- more markup -->
<div id="root"></div>
</body>
</html>
Tailwind CSSを初期化するには、次のコマンドを実行します。
npx tailwindcss init -p
これにより、Tailwindの機能をカスタマイズおよび拡張するために使用されるファイルtailwind.config.js
とファイルが生成されます。postcss.config.js
今のところ、テンプレートパスを設定するだけです。tailwind.config.js
以下のコードで更新します。
// tailwind.config.js
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
次に、ディレクティブをindex.css
使用してTailwindレイヤーを追加します。@tailwind
// index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
これで、Tailwindのユーティリティクラスを使用してReactアプリのスタイリングを開始できます。まず、SearchBox
コンポーネントを画面の上部中央に配置します。
で、フォルダをsrc
作成してファイルを保存します。次に、このコンポーネントをにインポートし、以下のTailwindクラスを親コンテナに適用します。componentsSearchBox.jsApp.js
// App.js
import SearchBox from './components/SearchBox'
import './App.css'
function App() {
return (
<div className='m-auto relative top-28 w-11/12 sm:w-6/12'>
<SearchBox />
</div>
)
}
export default App
これにより、検索ボックスがページの上部中央に配置されます。
Turnstone
検索ボックスの動的部分の構成を開始する前に、Turnstone
コンポーネントに次のプロパティを追加します。
// SearchBox.js
import Turnstone from 'turnstone'
const SearchBox = () => {
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
/>
)
}
export default SearchBox
clearButton
ユーザーが検索ボックスに文字を入力するたびに、クリアボタンをレンダリングします。
autoFocustrue
ページの読み込み時に検索ボックスが自動的にフォーカスを受け取るように設定します。
maxItems
に表示される検索結果の最大数listbox
を6に設定します。
listboxIsImmutabletrue
の内容がlistbox
クエリ間で変更されないように設定します。つまり、同じクエリで異なる結果を返すことはできません。
listbox
それでは、プロパティに移りましょう。
listbox
リストボックスのdata
プロパティで、Comics APIにリクエストを送信し、プロセスで現在のクエリ文字列とAPIキーを添付します。
// SearchBox.js
import Turnstone from 'turnstone'
const SearchBox = () => {
const listbox = {
displayField: 'characters',
data: async (query) => {
const res = await fetch(
`https://gateway.marvel.com:443/v1/public/characters?nameStartsWith=${query}&apikey=${process.env.REACT_APP_MARVEL_APIKEY}`
)
const data = await res.json()
return data.data.results
},
searchType: 'startsWith',
}
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
listbox={listbox}
/>
)
}
export default SearchBox
Marvel APIには、使用可能なすべてのエンドポイントが一覧表示されたインタラクティブドキュメントページがあります。この例では、キャラクターのエンドポイントにリクエストを送信しました/v1/public/characters
。
、、、などstories
の追加パラメータを追加して、さまざまな結果を得ることができます。また、パラメータを使用して、その値を文字列に設定します。eventsnameStartsWithnameStartsWithquery
この関数の結果は、名前が文字列results
で始まるすべてのMarvel文字の配列を含むオブジェクトになります。query
// JSON result from API call. query="Doctor Strange"
{
"code": 200,
"status": "Ok",
"copyright": "© 2022 MARVEL",
"attributionText": "Data provided by Marvel. © 2022 MARVEL",
"attributionHTML": "<a href=\"http://marvel.com\">Data provided by Marvel. © 2022 MARVEL</a>",
"etag": "07a3a76164eec745484f34562db7ca7166c196cc",
"data": {
"offset": 0,
"limit": 20,
"total": 2,
"count": 2,
"results": [
{
"id": 1009282,
"name": "Doctor Strange",
"description": "",
// ...
関連するデータはdata.results
、関数の戻り値であるにあります。
この時点で、アプリケーションは正しく機能します。これで、Tailwindとstyles
プロパティを使用してTurnstoneの要素のスタイルを設定できます。
Turnstone
要素styles
前に説明したように、オブジェクトのキーは検索コンポーネントの特定の要素を表します。listbox
、のハイライトされたアイテム、オートコンプリートテキストの色などの要素のスタイルを設定listbox
して、見栄えの良い検索ボックスを作成できます。
// SearchBox.js
import Turnstone from 'turnstone'
import recentSearchesPlugin from 'turnstone-recent-searches'
const listbox = {
// ...
}
const styles = {
input: 'w-full border py-2 px-4 text-lg outline-none rounded-md',
listbox: 'bg-neutral-900 w-full text-slate-50 rounded-md',
highlightedItem: 'bg-neutral-800',
query: 'text-oldsilver-800 placeholder:text-slate-600',
typeahead: 'text-slate-500',
clearButton:
'absolute inset-y-0 text-lg right-0 w-10 inline-flex items-center justify-center bg-netural-700 hover:text-red-500',
noItems: 'cursor-default text-center my-20',
match: 'font-semibold',
groupHeading: 'px-5 py-3 text-pink-500',
}
const SearchBox = () => {
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
listbox={listbox}
styles={styles}
/>
)
}
export default SearchBox
Item
コンポーネントプロップのプロパティをlistbox
参照することでアイテムのスタイルを設定できますが、Turnstoneには、Turnstone要素の追加のカスタマイズとフォーマットを基本的に可能にするコンポーネントプロパティが用意されています。itemstyles
これを使用して、検索結果のキャラクター名の横にアバターを含める方法は次のとおりです。
// SearchBox.js
import Turnstone from 'turnstone'
const listbox = {
// ...
}
const styles = {
// ...
}
const Item = ({ item }) => {
/* thubmnails from the API are stored as partials therefore
we have to concatenate the image path with its extension
*/
const avatar = `${item.thumbnail.path}.${item.thumbnail.extension}`
return (
<div className='flex items-center cursor-pointer px-5 py-4'>
<img
width={35}
height={35}
src={avatar}
alt={item.name}
className='rounded-full object-cover mr-3'
/>
<p>{item.name}</p>
</div>
)
}
const SearchBox = () => {
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
listbox={listbox}
styles={styles}
Item={Item}
/>
)
}
export default SearchBox
追加(1.7kB gzip)の場合、turnstone-recent-searches
Turnstoneのプラグインプロップに呼び出される別のパッケージを追加して、ユーザーの最近の検索を自動的に記録できます。
次のコマンドを使用して、このパッケージをインストールします。
npm install turnstone-recent-searches
そしてそれをそのplugins
ように小道具に含めてください:
// SearchBox.js
import Turnstone from 'turnstone'
import recentSearchesPlugin from 'turnstone-recent-searches'
const styles = {
input: 'w-full border py-2 px-4 text-lg outline-none rounded-md',
listbox: 'bg-neutral-900 w-full text-slate-50 rounded-md',
highlightedItem: 'bg-neutral-800',
query: 'text-oldsilver-800 placeholder:text-slate-600',
typeahead: 'text-slate-500',
clearButton:
'absolute inset-y-0 text-lg right-0 w-10 inline-flex items-center justify-center bg-netural-700 hover:text-red-500',
noItems: 'cursor-default text-center my-20',
match: 'font-semibold',
groupHeading: 'px-5 py-3 text-pink-500',
}
const listbox = {
displayField: 'characters',
data: async (query) => {
const res = await fetch(
`https://gateway.marvel.com:443/v1/public/characters?nameStartsWith=${query}&apikey=${process.env.REACT_APP_MARVEL_APIKEY}`
)
const data = await res.json()
return data.data.results
},
searchType: 'startsWith',
}
const Item = ({ item }) => {
const avatar = `${item.thumbnail.path}.${item.thumbnail.extension}`
return (
<div className='flex items-center cursor-pointer px-5 py-4'>
<img
width={35}
height={35}
src={avatar}
alt={item.name}
className='rounded-full object-cover mr-3'
/>
<p>{item.name}</p>
</div>
)
}
const SearchBox = () => {
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
listbox={listbox}
styles={styles}
Item={Item}
plugins={[recentSearchesPlugin]}
/>
)
}
export default SearchBox
この機能は、ユーザーのエクスペリエンスを向上させるため、同様に重要です。
オートコンプリート検索ボックスは最新のUIデザインで普及しており、それらを簡単に実装するのに役立つReactライブラリがあることは素晴らしいことです。
Turnstoneのドキュメントは、API設計をうまく説明しており、徐々に学習曲線を示しています。これは、他のReactオートコンプリートライブラリを試したときはそうではありませんでした。ターンストーンの実際の例をもっと見るには、ターンストーンのウェブサイトで例をチェックしてください。
このストーリーは、もともとhttps://blog.logrocket.com/create-customizable-react-search-component-autocomplete/で公開されました
1655799240
La mayoría de las aplicaciones frontend actuales requieren un cuadro de búsqueda de algún tipo, que a veces es el primer componente con el que un usuario interactúa en su página, por ejemplo, Airbnb, Uber o Google Maps. La creación de un componente de búsqueda que no solo funcione, sino que sea lo suficientemente funcional para guiar a su usuario en la realización de la tarea deseada, es vital para la experiencia de usuario de su aplicación.
Turnstone es una nueva biblioteca que permite a los desarrolladores de React hacer precisamente eso. Esta biblioteca liviana (12.2kB Gzip) se envía con autocompletado, almacenamiento en caché automatizado, accesibilidad WAI-ARIA y otras funciones que le permiten crear un componente de búsqueda funcional y accesible.
Los elementos de Turnstone se pueden personalizar fácilmente utilizando varios métodos CSS, como módulos CSS o Tailwind CSS . En este artículo, usaremos Turnstone y Tailwind para crear una aplicación que consulte la API de Marvel Comics para buscar personajes pertenecientes al Universo Cinematográfico de Marvel.
Vea el proyecto en vivo y el código fuente en GitHub.
Para seguir este tutorial, necesitará:
El conocimiento de Tailwind CSS es apreciado, pero no obligatorio.
Dirígete al sitio de Marvel Developers y registra una nueva cuenta. Luego navegue a Mi cuenta de desarrollador donde encontrará sus claves API públicas y privadas. Copie la clave pública para más tarde.
Antes de que su aplicación pueda realizar una solicitud a esta API, su nombre de dominio debe incluirse en su lista de sitios de referencia. En la misma página, desplácese hacia abajo hasta la sección de sitios de referencia y agregue el nombre de dominio de su aplicación, es decir, localhost
. También puede usar un asterisco *
para aceptar solicitudes de todos los dominios, aunque esto no es del todo seguro.
¡Ahora ya está todo listo para comenzar a desarrollar!
El Turnstone
componente acepta una variedad de propiedades que se utilizan para controlar diferentes partes del cuadro de búsqueda. Todo, desde el estilo del componente, la fuente de datos de la que consulta el cuadro de búsqueda, los mensajes de error y más, se puede configurar con la propiedad adecuada.
Repasemos algunos de los más importantes que usaremos en la creación de esta aplicación.
typeahead
Tipo: boolean
typeahead
- también conocido como autocompletar - es una función en la que una aplicación predice el resto de una palabra que está escribiendo un usuario. Esto está configurado true
de forma predeterminada.
maxItems
Tipo: number
esta propiedad controla el número máximo de resultados de búsqueda que se muestran en el archivo listbox
.
listbox
Escriba: object
, array
o function
listbox
especifica cómo se representan los resultados en respuesta a la consulta de un usuario. Esta propiedad controla el origen de los datos, así como el tipo de búsqueda, que podría ser startsWith
o contains
.
Como objeto, listbox
consulta una sola fuente de datos como tal:
const listbox = {
displayField: 'characters',
data: (query) =>
fetch(`/api/characters?q=${query}`)
.then(response => response.json()),
searchType: 'startsWith',
}
return (
<Turnstone listbox={listbox} />
)
data
arriba hay una función cuyo valor devuelto debe ser una Promesa que se resuelve en una matriz de elementos. Esta función toma la query
cadena actual como argumento y se vuelve a ejecutar cada vez que cambia la cadena, razón por la cual necesitamos otra propiedad llamada debounceWait
, más sobre eso momentáneamente.
Si se usa como una matriz, listbox
puede recopilar datos de múltiples fuentes:
const listbox = [
{
id: 'cities',
name: 'Cities',
ratio: 8,
displayField: 'name',
data: (query) =>
fetch(`/api/cities?q=${encodeURIComponent(query)}`)
.then(response => response.json()),
searchType: 'startswith'
},
{
id: 'airports',
name: 'Airports',
ratio: 2,
displayField: 'name',
data: (query) =>
fetch(`/api/airports?q=${encodeURIComponent(query)}`)
.then(response => response.json()),
searchType: 'contains'
}
]
return (
<Turnstone listbox={listbox} />
)
En este escenario, ratio
se puede utilizar una propiedad para especificar el número de resultados que ocupa listbox
en relación con maxItems
. Esto significa que, si maxItems
se establece en 10, por ejemplo, el ratio
número de cada fuente de datos debe sumar 10.
styles
Tipo: object
un objeto cuyas claves representan elementos representados por Turnstone. Cada valor correspondiente es una cadena que representa el class
atributo del elemento .
const styles = {
input: 'w-full h-12 border border-slate-300 py-2 pl-10',
listbox: 'w-full bg-white sm:border sm:border-blue-300 sm:rounded text-left sm:mt-2 p-2 sm:drop-shadow-xl',
groupHeading: 'cursor-default mt-2 mb-0.5 px-1.5 uppercase text-sm text-rose-300',
}
return (
<Turnstone styles={styles} />
)
Podemos ver con qué facilidad se adapta Tailwind para facilitar el proceso de diseño. Vea la lista de elementos Turnstone disponibles en los documentos .
debounceWait
Tipo: number
esta propiedad especifica el tiempo de espera, en milisegundos, después de que el usuario termine de escribir antes de que su consulta se envíe a la fetch
función.
defaultListbox
Esta propiedad es idéntica listbox
pero se muestra cuando el cuadro de búsqueda está enfocado, sin una cadena de consulta. Suele utilizarse para crear un listbox
para búsquedas recientes:
const defaultListBox = {
displayField: 'Recent Searches',
data: () => Promise.resolve(JSON.parse(localStorage.getItem('recentSearches')) || [])
}
return (
<Turnstone defaultListBox={defaultListBox} />
)
Abre tu terminal y crea una nueva aplicación React con el siguiente comando:
npx create-react-app turnstone-demo
Una vez completada la instalación, navegue hasta el directorio del proyecto:
cd turnstone-demo
E instale Turnstone y Tailwind CSS, junto con sus dependencias de pares, PostCSS y Autoprefixer :
npm install -D turnstone tailwindcss postcss autoprefixer
Comencemos creando una variable de entorno para la clave API. En la raíz de su proyecto, cree un .env
archivo y almacene la clave API
// .env
REACT_APP_MARVEL_APIKEY = 'your_apikey_here'
Create React App brinda soporte para variables ambientales, que se crean con el REACT_APP_
prefijo requerido. Luego se puede acceder a esta variable dentro de la aplicación como process.env.REACT_APP_MARVEL_APIKEY
.
NB , recuerde agregar .env
a su .gitignore
archivo para que no exponga su clave en un repositorio público.
El fondo de la imagen subyacente, como se ve en la demostración del proyecto, se crea con la siguiente clase CSS:
// App.css
.image-backdrop {
background-image: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('../public/comic-backdrop.jpg');
height: 100vh;
width: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
Adjunte esta clase a la body
etiqueta public/index.html
y debería tener una imagen de fondo para colocar el cuadro de búsqueda:
// public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<!-- markup -->
</head>
<body class="image-backdrop">
<!-- more markup -->
<div id="root"></div>
</body>
</html>
Para inicializar Tailwind CSS, ejecute el siguiente comando:
npx tailwindcss init -p
Esto genera los archivos tailwind.config.js
y postcss.config.js
, que se utilizan para personalizar y ampliar las funciones de Tailwind.
Solo necesitamos configurar las rutas de la plantilla por ahora. Actualice tailwind.config.js
con el siguiente código:
// tailwind.config.js
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
A continuación, agregue las capas Tailwind para index.css
usar la @tailwind
directiva:
// index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
Y ahora puede comenzar a diseñar su aplicación React con las clases de utilidad de Tailwind. Comencemos colocando el SearchBox
componente en la parte superior central de la pantalla.
En src
, cree una components
carpeta y almacene el SearchBox.js
archivo. A continuación, importe este componente App.js
y aplique las siguientes clases de Tailwind al contenedor principal:
// App.js
import SearchBox from './components/SearchBox'
import './App.css'
function App() {
return (
<div className='m-auto relative top-28 w-11/12 sm:w-6/12'>
<SearchBox />
</div>
)
}
export default App
Esto coloca el cuadro de búsqueda en la parte superior central de la página.
Turnstone
componenteAntes de comenzar a configurar las partes dinámicas del cuadro de búsqueda, agregue las siguientes propiedades al Turnstone
componente:
// SearchBox.js
import Turnstone from 'turnstone'
const SearchBox = () => {
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
/>
)
}
export default SearchBox
clearButton
muestra un botón claro cada vez que el usuario ingresa un carácter en el cuadro de búsqueda.
autoFocus
establecido en true
hace que el cuadro de búsqueda reciba automáticamente el foco cuando se carga la página.
maxItems
establece el número máximo de resultados de búsqueda que se mostrarán en listbox
6.
listboxIsImmutable
establecido en true
asegura que el contenido de listbox
no cambie entre consultas; es decir, la misma consulta no puede devolver resultados diferentes.
Ahora pasemos a la listbox
propiedad.
listbox
En la data
propiedad de listbox, hacemos una solicitud a la API de Comics, adjuntando la cadena de consulta actual y su clave de API en el proceso:
// SearchBox.js
import Turnstone from 'turnstone'
const SearchBox = () => {
const listbox = {
displayField: 'characters',
data: async (query) => {
const res = await fetch(
`https://gateway.marvel.com:443/v1/public/characters?nameStartsWith=${query}&apikey=${process.env.REACT_APP_MARVEL_APIKEY}`
)
const data = await res.json()
return data.data.results
},
searchType: 'startsWith',
}
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
listbox={listbox}
/>
)
}
export default SearchBox
La API de Marvel tiene una página de documentación interactiva donde se enumeran todos los puntos finales disponibles. En nuestro caso, hemos realizado una solicitud al extremo de caracteres: /v1/public/characters
.
Se pueden agregar parámetros adicionales como stories
, events
o para obtener resultados diferentes. nameStartsWith
También usamos el nameStartsWith
parámetro, estableciendo su valor en la query
cadena.
El resultado de esta función debería ser un objeto que contenga una results
matriz de todos los personajes de Marvel cuyo nombre comience con la query
cadena:
// JSON result from API call. query="Doctor Strange"
{
"code": 200,
"status": "Ok",
"copyright": "© 2022 MARVEL",
"attributionText": "Data provided by Marvel. © 2022 MARVEL",
"attributionHTML": "<a href=\"http://marvel.com\">Data provided by Marvel. © 2022 MARVEL</a>",
"etag": "07a3a76164eec745484f34562db7ca7166c196cc",
"data": {
"offset": 0,
"limit": 20,
"total": 2,
"count": 2,
"results": [
{
"id": 1009282,
"name": "Doctor Strange",
"description": "",
// ...
Los datos relevantes se encuentran en data.results
, que es el valor de retorno de la función.
En este punto, la aplicación funciona correctamente. Ahora podemos proceder a diseñar los elementos de Turnstone con Tailwind y la styles
propiedad.
Turnstone
con TailwindComo se explicó anteriormente, las claves en el styles
objeto representan un cierto elemento del componente de búsqueda. Podemos diseñar elementos como listbox
, elementos resaltados en listbox
e incluso el color del texto de autocompletar para crear un cuadro de búsqueda más atractivo:
// SearchBox.js
import Turnstone from 'turnstone'
import recentSearchesPlugin from 'turnstone-recent-searches'
const listbox = {
// ...
}
const styles = {
input: 'w-full border py-2 px-4 text-lg outline-none rounded-md',
listbox: 'bg-neutral-900 w-full text-slate-50 rounded-md',
highlightedItem: 'bg-neutral-800',
query: 'text-oldsilver-800 placeholder:text-slate-600',
typeahead: 'text-slate-500',
clearButton:
'absolute inset-y-0 text-lg right-0 w-10 inline-flex items-center justify-center bg-netural-700 hover:text-red-500',
noItems: 'cursor-default text-center my-20',
match: 'font-semibold',
groupHeading: 'px-5 py-3 text-pink-500',
}
const SearchBox = () => {
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
listbox={listbox}
styles={styles}
/>
)
}
export default SearchBox
Item
accesorio de componenteAunque podemos diseñar elementos listbox
haciendo referencia a la item
propiedad en styles
, Turnstone proporciona propiedades de componentes que esencialmente permiten una personalización y formato adicionales de los elementos de Turnstone.
Así es como podemos usar esto para incluir un avatar junto al nombre del personaje en un resultado de búsqueda:
// SearchBox.js
import Turnstone from 'turnstone'
const listbox = {
// ...
}
const styles = {
// ...
}
const Item = ({ item }) => {
/* thubmnails from the API are stored as partials therefore
we have to concatenate the image path with its extension
*/
const avatar = `${item.thumbnail.path}.${item.thumbnail.extension}`
return (
<div className='flex items-center cursor-pointer px-5 py-4'>
<img
width={35}
height={35}
src={avatar}
alt={item.name}
className='rounded-full object-cover mr-3'
/>
<p>{item.name}</p>
</div>
)
}
const SearchBox = () => {
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
listbox={listbox}
styles={styles}
Item={Item}
/>
)
}
export default SearchBox
Por un extra (gzip de 1.7kB), turnstone-recent-searches
se puede agregar otro paquete llamado al complemento de Turnstone para registrar automáticamente las búsquedas recientes del usuario.
Instale este paquete con el siguiente comando:
npm install turnstone-recent-searches
E inclúyelo en el plugins
prop como tal:
// SearchBox.js
import Turnstone from 'turnstone'
import recentSearchesPlugin from 'turnstone-recent-searches'
const styles = {
input: 'w-full border py-2 px-4 text-lg outline-none rounded-md',
listbox: 'bg-neutral-900 w-full text-slate-50 rounded-md',
highlightedItem: 'bg-neutral-800',
query: 'text-oldsilver-800 placeholder:text-slate-600',
typeahead: 'text-slate-500',
clearButton:
'absolute inset-y-0 text-lg right-0 w-10 inline-flex items-center justify-center bg-netural-700 hover:text-red-500',
noItems: 'cursor-default text-center my-20',
match: 'font-semibold',
groupHeading: 'px-5 py-3 text-pink-500',
}
const listbox = {
displayField: 'characters',
data: async (query) => {
const res = await fetch(
`https://gateway.marvel.com:443/v1/public/characters?nameStartsWith=${query}&apikey=${process.env.REACT_APP_MARVEL_APIKEY}`
)
const data = await res.json()
return data.data.results
},
searchType: 'startsWith',
}
const Item = ({ item }) => {
const avatar = `${item.thumbnail.path}.${item.thumbnail.extension}`
return (
<div className='flex items-center cursor-pointer px-5 py-4'>
<img
width={35}
height={35}
src={avatar}
alt={item.name}
className='rounded-full object-cover mr-3'
/>
<p>{item.name}</p>
</div>
)
}
const SearchBox = () => {
return (
<Turnstone
id='search'
name='search'
autoFocus={true}
typeahead={true}
clearButton={true}
debounceWait={250}
listboxIsImmutable={true}
maxItems={6}
noItemsMessage="We couldn't find any character that matches your search"
placeholder='Search for any character in the MCU'
listbox={listbox}
styles={styles}
Item={Item}
plugins={[recentSearchesPlugin]}
/>
)
}
export default SearchBox
Esta característica es igualmente importante ya que crea una mejor experiencia para su usuario.
Los cuadros de búsqueda de autocompletar prevalecen en el diseño moderno de la interfaz de usuario, y tener una biblioteca React que nos ayude a implementarlos fácilmente es excelente.
La documentación de Turnstone hace un buen trabajo al explicar el diseño de su API, dándole una curva de aprendizaje gradual, que no fue el caso cuando probé otras bibliotecas de autocompletado de React. Para ver más ejemplos de Turnstone en acción, consulte los ejemplos en el sitio web de Turnstone .
Esta historia se publicó originalmente en https://blog.logrocket.com/create-customizable-react-search-component-autocomplete/
1654806540
ちょうど1年前に「Reactでコンポーネントを検索してフィルタリングする方法」という記事を書きました。
それ以来、多くの変化がありました。チュートリアルで使用したAPIが機能しなくなったため、この記事では、コンポーネントにページ付けを導入しながら、以前の例を再作成します。
このチュートリアルではReact.jsを使用しているため、従うにはReactとJavaScriptの基本を理解する必要があります。このチュートリアルは、前の記事「 Reactでコンポーネントを検索およびフィルタリングする方法」を読んだことも前提としています。
このチュートリアルの国データを取得するには、CountryAPI.io国APIを使用します。
APIを使用するにはAPIキーが必要です。APIキーを取得するには、CountryAPI.ioにアクセスしてアカウントを作成します。APIキーがダッシュボードに表示されます。
次に、CreateReactAppを使用して新しいReactアプリを作成します。これを行うには、ターミナルで次を実行します。
# Run this to use npm
npx create-react-app search-app
# Or run this to use yarn
yarn create react-app search-app
cd my-app
npm start
# Or with yarn
yarn start
いつものように、ライブプレビューでは、Codepenを使用してすべての例を表示します。
https://countryapi.io/api/all
データを取得するには、APIキーを指定しながらエンドポイントに対してGET呼び出しを行う必要があります。以前に作成したReactアプリのsrc > App.jsファイルで、既存のコードをすべて削除し、次のように置き換えます。
import { useState, useEffect } from "react";
import "./App.css";
function App() {
const [error, setError] = useState(null);
const [loaded, setLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
const request_headers = new Headers();
const api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
request_headers.append("Authorization", `Bearer ${api_key}`);
request_headers.append("Content-Type", "application/json");
const request_options = {
method: "GET",
headers: request_headers,
};
fetch("https://countryapi.io/api/all", request_options)
.then((res) => res.json())
.then(
(result) => {
setLoaded(true);
setItems(result);
},
(error) => {
setLoaded(true);
setError(error);
}
);
}, []);
console.log(items)
if (error) {
return <>{error.message}</>;
} else if (!loaded) {
return <>loading...</>;
} else {
return (
//
);
}
}
export default App;
上記で行ったのは、JavaScriptフェッチAPIを使用してエンドポイントにGETリクエストを行い、返されたデータをitems
を使用して状態に保存することだけsetState(result)
でした。
次に、APIデータを表示する必要があります。これは、APIによって返されるすべての国のリストになります。
リストを作成するには、返されたオブジェクトの値からオブジェクトの配列を生成する必要があります。src> App.jsファイルを開き、次のコードを追加します。
import { useState, useEffect } from "react";
import "./App.css";
function App() {
const [error, setError] = useState(null);
const [loaded, setLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
// fetch data
}, []);
const data = Object.values(items);
if (error) {
return <>{error.message}</>;
} else if (!loaded) {
return <>loading...</>;
} else {
return (
<div className="wrapper">
<ul className="card-grid">
{data.map((item) => (
<li key={item.alpha3Code}>
<article className="card">
<div className="card-image">
<img src={item.flag.large} alt={item.name} />
</div>
<div className="card-content">
<h2 className="card-name">{item.name}</h2>
// other card content
</article>
</li>
))}
</ul>
</div>
);
}
}
export default App;
CSSを追加すると、上記の例は以下のプレビューのようになります。
まず、ユーザーが検索クエリを入力できる入力フィールドを作成します。src > App.jsを開き、次の編集を行います。
...
function App() {
const [query, setQuery] = useState("");
if (error) {
return <>{error.message}</>;
} else if (!loaded) {
return <>loading...</>;
} else {
return (
<div className="wrapper">
<div className="search-wrapper">
<label htmlFor="search-form">
<input
type="search"
name="search-form"
id="search-form"
className="search-input"
placeholder="Search for..."
onChange={(e) => setQuery(e.target.value)}
/>
<span className="sr-only">Search countries here</span>
</label>
</div>
...
</div>
);
}
}
export default App;
上記で入力フィールドを作成し、onChange
イベントハンドラーを使用して、入力フィールドの値が変更されるたびに、フックをquery
使用するように値を設定しました。useState
次に、それを使用しquery
てAPIから返されるデータをフィルタリングする必要があります。
const search_parameters = Object.keys(Object.assign({}, ...data));
function search(data) {
return items.filter(
(item) =>
search_parameters.some((parameter) =>
item[parameter].toString().toLowerCase().includes(query)
)
);
}
これを少し分解してみましょう。まず、引数としてsearch()
取り入れる関数を作成しました。検索パラメータのいずれかにクエリの値が含まれているかどうかを確認したメソッドとメソッドをdata
組み合わせます。Array.filter()Array.some()includes(query)
もちろん、検索パラメータをハードコーディングすることもできます。
const search_parameters = ["Capital", "Name", ...]
これは最速の方法ですが、将来を保証するものではありません(APIから返されるデータは変更される可能性があります)–これは難しい方法であることがわかりました。data
したがって、ハードコーディングする代わりに、APIから返されたものから検索パラメーターを取得できます。
const search_parameters = Object.keys(Object.assign({}, ...data));
最後に、search(data)
関数から返された新しいデータを使用して国リストを作成する必要があります。App.jsファイルを開き、前に作成したリストを編集します。
...
{search(data).map((item) => (
<li key={item.alpha3Code}>
<article className="card">
<div className="card-image">
<img src={item.flag.large} alt={item.name} />
</div>
<div className="card-content">
<h2 className="card-name">{item.name}</h2>
...
</div>
</article>
</li>
))}
検索機能が追加されると、ライブプレビューは次のようになります。
フィルタは、特定のキーワードでデータをグループ化するためによく使用されます。この例では、地域ごとにデータをグループ化します。
繰り返しますが、これをハードコーディングする代わりに、データからリージョンを取得できます。
const filter_items = [...new Set(data.map((item) => item.region))];
作成した検索入力フィールドの後に、フィルターUI(HTMLselect
入力フィールド)を追加します。
...
const [filter, setFilter] = useState("");
...
<div className="select">
<select
onChange={(e) => setFilter(e.target.value)}
className="custom-select"
aria-label="Filter Countries By Region">
<option value="">Filter By Region</option>
{filter_items.map((item) => (
<option value={item}>Filter By {item}</option>
))}
</select>
<span className="focus"></span>
</div>
フィルタを追加するには、search(data)
関数を変更する必要があります。したがって、検索したデータのみを返すのではなく、フィルターパラメーターとしてデータを返すようになりました。
function search(items) {
return items.filter(
(item) =>
item.region.includes(filter) &&
search_parameters.some((parameter) =>
item[parameter].toString().toLowerCase().includes(query)
)
);
}
これで、国を地域でフィルタリングできるようになりました。ライブプレビューと完全なコードはCodepenで見つけることができます:
ページ付けは、Webページに表示されるアイテムのリストを減らすだけでなく、ページの読み込み時にユーザーがダウンロードする必要のあるリソースが少ないため、アプリのパフォーマンスも向上します。
国データのページネーションを作成するには、まず、表示するアイテムの数を指定しますuseState
。
const [paginate, setpaginate] = useState(8);
次に、paginate
値を使用して国リストを更新しましょう。
...
{search(data)
.slice(0, paginate)
.map((item) => (
<li key={item.alpha3Code}>
<article className="card">
<div className="card-image">
<img src={item.flag.large} alt={item.name} />
</div>
<div className="card-content">
<h2 className="card-name">{item.name}</h2>
...
</div>
</article>
</li>
))}
次に、この状態を呼び出すたびにこの状態を更新する関数を作成する必要があります。
const load_more = (event) => {
setpaginate((prevValue) => prevValue + 8);
};
load_more
最後に、クリックしたときに関数を呼び出すボタンを作成しましょう。
<button onClick={load_more}>Load More</button>
繰り返しますが、ページネーションのプレビューはCodePenで見つけることができます:
この記事では、CountryAPI.ioを使用して実際のアプリを構築することにより、Reactで検索、フィルター、およびページ付け機能を実装する方法について説明しました。
これで素晴らしいものを作成した場合は、遠慮なくツイートして、@sprucekhalifaのタグを付けてください。そして、フォローボタンを押すことを忘れないでください。
ハッピーコーディング!
このストーリーは、もともとhttps://www.freecodecamp.org/news/how-to-react-components/で公開されました