├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ └── custom.md ├── .gitignore ├── .storybook ├── main.js ├── manager.js └── rtwTheme.js ├── README.md ├── nwb.config.js ├── package.json ├── src ├── index.d.ts ├── index.js └── utils.js ├── stories ├── 0-Follow.stories.js ├── 1-Hashtag.stories.js ├── 2-Mention.stories.js ├── 3-Share.stories.js ├── 4-Timeline.stories.js ├── 5-Tweet.stories.js └── 6-Misc.stories.js └── tests ├── .eslintrc └── index.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig 2 | # See: http://editorconfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Match all files 8 | # Unix-style newlines with a newline ending every file, 9 | # 4-space indentation 10 | [*] 11 | end_of_line = lf 12 | insert_final_newline = true 13 | charset = utf-8 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Questions and Help 4 | url: https://twittercommunity.com 5 | about: This issue tracker is not for support questions. Please use twittercommunity.com or the Discussions tab. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report or Feature Request 3 | about: 'Bugs or feature requests specifically for react-twitter-widgets' 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | *READ BEFORE OPENING AN ISSUE* 11 | 12 | react-twitter-widgets is a thin wrapper around the official Twitter for Websites factory functions. 13 | 14 | This is remote code that can and has changed on a whim. Bugs are often introduced. 15 | 16 | Do not open issues here for bugs or questions related to the actual widget. For that, you can use https://twittercommunity.com/. 17 | 18 | Open an issue *if it occurs only with react-twitter-widgets and not the vanilla factory functions* (use Codepen or similar to test). 19 | 20 | Delete this entire message before writing your issue. 21 | 22 | Thank you. 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /es 3 | /lib 4 | /node_modules 5 | /umd 6 | npm-debug.log* 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ["../stories/**/*.stories.js"], 3 | addons: ["@storybook/addon-storysource"], 4 | }; 5 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from "@storybook/addons"; 2 | import rtwTheme from "./rtwTheme"; 3 | 4 | addons.setConfig({ 5 | theme: rtwTheme 6 | }); 7 | -------------------------------------------------------------------------------- /.storybook/rtwTheme.js: -------------------------------------------------------------------------------- 1 | import { create } from "@storybook/theming/create"; 2 | 3 | export default create({ 4 | brandTitle: "react-twitter-widgets", 5 | brandUrl: "https://github.com/andrewsuzuki/react-twitter-widgets" 6 | }); 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-twitter-widgets 2 | 3 | [![npm version](https://badge.fury.io/js/react-twitter-widgets.svg)](https://badge.fury.io/js/react-twitter-widgets) 4 | [![npm downloads](https://img.shields.io/npm/dm/react-twitter-widgets.svg)](https://badge.fury.io/js/react-twitter-widgets) 5 | [![GitHub issues](https://img.shields.io/github/issues/andrewsuzuki/react-twitter-widgets)](https://github.com/andrewsuzuki/react-twitter-widgets/issues) 6 | 7 | Quick and easy Twitter widgets for React. 8 | 9 | Available widgets: `Timeline`, `Share`, `Follow`, `Hashtag`, `Mention`, `Tweet`. 10 | 11 | See below for usage. 12 | 13 | ## Demo 14 | 15 | **[Storybook / Live Demo](https://andrewsuzuki.github.io/react-twitter-widgets/)** 16 | 17 | ## Installation 18 | 19 | ``` 20 | npm install --save react-twitter-widgets 21 | ``` 22 | 23 | ## Example 24 | 25 | ```javascript 26 | import { Timeline } from 'react-twitter-widgets' 27 | 28 | // Tweet (without options) 29 | 30 | 31 | // Timeline (with options) 32 | 41 | ``` 42 | 43 | ## Usage 44 | 45 | [**🔗 Official Twitter Documentation**](https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/guides/scripting-factory-functions) 46 | 47 | Available widgets: `Timeline`, `Share`, `Follow`, `Hashtag`, `Mention`, `Tweet` 48 | 49 | **`Timeline`** requires a `dataSource` object prop. The source type can be `profile`, `list`, or `url`. They each require their own co-fields; see Twitter documentation. NOTE that `collection`, `likes`, and `moments` will be [deprecated](https://twittercommunity.com/t/removing-support-for-embedded-like-collection-and-moment-timelines/150313) on June 23, 2021. 50 | 51 | **`Share`** requires a `url` prop. 52 | 53 | **`Follow`** and **`Mention`** require a `username` prop. NOTE that the Twitter 54 | documentation now refers to this as _screenName_. 55 | 56 | **`Hashtag`** requires a `hashtag` prop. 57 | 58 | **`Tweet`** requires a `tweetId` prop. Ex. `'511181794914627584'` 59 | 60 | ### Common Props 61 | 62 | All widgets accept these props. 63 | 64 | - `options` (object) 65 | - To learn more about the available options, refer to the Twitter documentation. There are four options that are common to all widgets (`lang`, `dnt`, `related`, and `via`). There are further options for button widgets, tweet buttons, Timeline, and Tweet. 66 | - `onLoad` (function) 67 | - Called every time the widget is loaded. A widget will reload if its props change. 68 | - `renderError` (function) 69 | - Render prop. Rendered if widget cannot be loaded (no internet connection, screenName not found, bad props, etc). 70 | - Example: `renderError={(_err) =>

Could not load timeline

}` 71 | 72 | ### Lazy vs. Eager Loading 73 | 74 | By default, the remote Twitter library will be lazy-loaded when the first widget renders. To instead load it eagerly, call `eagerLoadTwitterLibrary`. 75 | 76 | ```js 77 | import { eagerLoadTwitterLibrary } from "react-twitter-widgets"; 78 | eagerLoadTwitterLibrary(); 79 | ``` 80 | 81 | ## Further Information 82 | 83 | - This library loads the remote _Twitter for Websites_ library. 84 | - Twitter widgets are only loaded in the browser. A blank div will be rendered during SSR. 85 | 86 | ## Contributing 87 | 88 | 1. Fork it! 89 | 2. Create your feature branch: `git checkout -b my-new-feature` 90 | 3. Commit your changes: `git commit -am 'Add some feature'` 91 | 4. Push to the branch: `git push origin my-new-feature` 92 | 5. Submit a pull request 93 | 94 | ## Credits 95 | 96 | - Andrew Suzuki - @andrewsuzuki - [andrewsuzuki.com](http://andrewsuzuki.com) 97 | 98 | ## License 99 | 100 | MIT 101 | -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-component', 3 | npm: { 4 | esModules: true, 5 | umd: { 6 | global: 'ReactTwitterWidgets', 7 | externals: { 8 | react: 'React' 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-twitter-widgets", 3 | "version": "1.11.0", 4 | "description": "Twitter widgets as React components", 5 | "author": "Andrew Suzuki", 6 | "license": "MIT", 7 | "bugs": { 8 | "url": "https://github.com/andrewsuzuki/react-twitter-widgets/issues" 9 | }, 10 | "homepage": "https://github.com/andrewsuzuki/react-twitter-widgets", 11 | "main": "lib/index.js", 12 | "module": "es/index.js", 13 | "types": "src/index.d.ts", 14 | "files": [ 15 | "src/index.d.ts", 16 | "es", 17 | "lib", 18 | "umd" 19 | ], 20 | "dependencies": { 21 | "loadjs": "^4.2.0" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.10.2", 25 | "@storybook/addon-storysource": "^5.3.19", 26 | "@storybook/addons": "^5.3.19", 27 | "@storybook/react": "^5.3.19", 28 | "@storybook/storybook-deployer": "^2.8.6", 29 | "@storybook/theming": "^5.3.19", 30 | "@types/react": "^16.9.35", 31 | "babel-loader": "^8.1.0", 32 | "nwb": "0.25.x", 33 | "react": "^16.13.1", 34 | "react-dom": "^16.13.1" 35 | }, 36 | "peerDependencies": { 37 | "react": "^16.8.0 || ^17 || ^18" 38 | }, 39 | "scripts": { 40 | "build": "nwb build-react-component", 41 | "clean": "nwb clean-module", 42 | "prepublishOnly": "npm run build", 43 | "start": "npm run storybook", 44 | "test": "nwb test-react", 45 | "test:coverage": "nwb test-react --coverage", 46 | "test:watch": "nwb test-react --server", 47 | "storybook": "start-storybook -p 6006", 48 | "build-storybook": "build-storybook", 49 | "deploy-storybook-ghpages": "storybook-to-ghpages" 50 | }, 51 | "repository": { 52 | "type": "git", 53 | "url": "git+https://github.com/andrewsuzuki/react-twitter-widgets.git" 54 | }, 55 | "keywords": [ 56 | "react", 57 | "component", 58 | "twitter", 59 | "embed", 60 | "widgets", 61 | "twttr", 62 | "timeline", 63 | "feed", 64 | "share", 65 | "tweet", 66 | "share", 67 | "follow", 68 | "hashtag", 69 | "button", 70 | "mention" 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent, ReactNode } from 'react' 2 | 3 | declare module 'react-twitter-widgets' { 4 | export type onLoad = () => any; 5 | export type renderError = (error: Error) => ReactNode; 6 | 7 | export interface FollowProps { 8 | username: string; 9 | options?: Object; 10 | onLoad?: onLoad; 11 | renderError?: renderError; 12 | } 13 | export const Follow: FunctionComponent; 14 | 15 | export interface HashtagProps { 16 | hashtag: string; 17 | options?: Object; 18 | onLoad?: onLoad; 19 | renderError?: renderError; 20 | } 21 | export const Hashtag: FunctionComponent; 22 | 23 | export interface MentionProps { 24 | username: string; 25 | options?: Object; 26 | onLoad?: onLoad; 27 | renderError?: renderError; 28 | } 29 | export const Mention: FunctionComponent; 30 | 31 | export interface ShareProps { 32 | url: string; 33 | options?: Object; 34 | onLoad?: onLoad; 35 | renderError?: renderError; 36 | } 37 | export const Share: FunctionComponent; 38 | 39 | export interface TimelineProps { 40 | dataSource: object; 41 | options?: Object; 42 | onLoad?: onLoad; 43 | renderError?: renderError; 44 | } 45 | export const Timeline: FunctionComponent; 46 | 47 | export interface TweetProps { 48 | tweetId: string; 49 | options?: Object; 50 | onLoad?: onLoad; 51 | renderError?: renderError; 52 | } 53 | export const Tweet: FunctionComponent; 54 | } 55 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from "react"; 2 | import { 3 | canUseDOM, 4 | useShallowCompareMemoize, 5 | removeChildrenWithAttribute, 6 | twWidgetFactory, 7 | cloneShallow 8 | } from "./utils"; 9 | 10 | export { loadTwitterLibrary as eagerLoadTwitterLibrary } from "./utils"; 11 | 12 | const childDivIdentifyingAttribute = "twdiv"; 13 | 14 | function useTwitterWidget(factoryFunctionName, primaryArg, options, onLoad) { 15 | const [error, setError] = useState(null); 16 | 17 | const ref = useRef(null); 18 | 19 | // noop if ssr 20 | if (!canUseDOM) { 21 | return { ref, error }; 22 | } 23 | 24 | // Make deps for useEffect. 25 | // options, and possibly primaryArg, are objects that should be compared (shallow). 26 | // There currently aren't any nested arrays or objects, so they 27 | // can be cloned in a shallow manner. 28 | // NOTE onLoad is used in useCallback, but it is not listed as a dependency. 29 | // Listing it would likely cause unnecessary loads. The latest onLoad should be 30 | // used regardless, since it will not be called unless the other dependencies 31 | // change, so it works out. 32 | const deps = [ 33 | factoryFunctionName, 34 | useShallowCompareMemoize(primaryArg), 35 | useShallowCompareMemoize(options) 36 | ]; 37 | 38 | useEffect(() => { 39 | // Reset error 40 | setError(null); 41 | 42 | // Protect against race conditions 43 | // (set to true in cleanup function; 44 | // checked if canceled in async loadWidget) 45 | let isCanceled = false; 46 | 47 | if (ref.current) { 48 | removeChildrenWithAttribute(ref.current, childDivIdentifyingAttribute); 49 | 50 | if (!ref || !ref.current) { 51 | return; 52 | } 53 | 54 | const childEl = document.createElement("div"); 55 | childEl.setAttribute(childDivIdentifyingAttribute, "yes"); 56 | ref.current.appendChild(childEl); 57 | 58 | twWidgetFactory() 59 | .then(wf => { 60 | // primaryArg (possibly an object) and options must be cloned 61 | // since twitter mutates them (gah!). 62 | // There currently aren't any nested arrays or objects, so they 63 | // can be cloned in a shallow manner. 64 | return wf[factoryFunctionName]( 65 | cloneShallow(primaryArg), 66 | childEl, 67 | cloneShallow(options) 68 | ); 69 | }) 70 | .then(resultMaybe => { 71 | // Twitter returns undefined if widget creation fails. 72 | // However, if deps are stale (isCanceled), suppress error (likely race condition). 73 | if (!resultMaybe && !isCanceled) { 74 | throw new Error( 75 | "Twitter could not create widget. If it is a Timeline or " + 76 | "Tweet, ensure the screenName/tweetId exists." 77 | ); 78 | } 79 | 80 | if (!ref || !ref.current) { 81 | return; 82 | } 83 | 84 | if (isCanceled) { 85 | if (childEl) { 86 | childEl.remove(); 87 | } 88 | return; 89 | } 90 | 91 | if (onLoad) { 92 | onLoad(); 93 | } 94 | }) 95 | .catch(e => { 96 | console.error(e); 97 | setError(e); 98 | }); 99 | } 100 | 101 | return () => { 102 | isCanceled = true; 103 | }; 104 | }, deps); 105 | 106 | return { ref, error }; 107 | } 108 | 109 | export const Follow = ({ username, options, onLoad, renderError }) => { 110 | const { ref, error } = useTwitterWidget( 111 | "createFollowButton", 112 | username, 113 | options, 114 | onLoad 115 | ); 116 | return
{error && renderError && renderError(error)}
; 117 | }; 118 | 119 | export const Hashtag = ({ hashtag, options, onLoad, renderError }) => { 120 | const { ref, error } = useTwitterWidget( 121 | "createHashtagButton", 122 | hashtag, 123 | options, 124 | onLoad 125 | ); 126 | return
{error && renderError && renderError(error)}
; 127 | }; 128 | 129 | export const Mention = ({ username, options, onLoad, renderError }) => { 130 | const { ref, error } = useTwitterWidget( 131 | "createMentionButton", 132 | username, 133 | options, 134 | onLoad 135 | ); 136 | return
{error && renderError && renderError(error)}
; 137 | }; 138 | 139 | export const Share = ({ url, options, onLoad, renderError }) => { 140 | const { ref, error } = useTwitterWidget( 141 | "createShareButton", 142 | url, 143 | options, 144 | onLoad 145 | ); 146 | return
{error && renderError && renderError(error)}
; 147 | }; 148 | 149 | export const Timeline = ({ dataSource, options, onLoad, renderError }) => { 150 | const { ref, error } = useTwitterWidget( 151 | "createTimeline", 152 | dataSource, 153 | options, 154 | onLoad 155 | ); 156 | return
{error && renderError && renderError(error)}
; 157 | }; 158 | 159 | export const Tweet = ({ tweetId, options, onLoad, renderError }) => { 160 | const { ref, error } = useTwitterWidget( 161 | "createTweet", 162 | tweetId, 163 | options, 164 | onLoad 165 | ); 166 | return
{error && renderError && renderError(error)}
; 167 | }; 168 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import loadjs from "loadjs"; 2 | import { useRef } from "react"; 3 | 4 | const twScriptUrl = "https://platform.twitter.com/widgets.js"; 5 | const twScriptWindowFieldName = "twttr"; 6 | const twScriptName = twScriptWindowFieldName; 7 | 8 | export const canUseDOM = !!( 9 | typeof window !== "undefined" && 10 | window.document && 11 | window.document.createElement 12 | ); 13 | 14 | export function loadTwitterLibrary() { 15 | if (!loadjs.isDefined(twScriptName)) { 16 | loadjs(twScriptUrl, twScriptName); 17 | } 18 | } 19 | 20 | export function twWidgetFactory() { 21 | return new Promise((resolve, reject) => { 22 | const rejectWithError = () => 23 | reject(new Error("Could not load remote twitter widgets js")); 24 | 25 | loadTwitterLibrary(); 26 | 27 | loadjs.ready(twScriptName, { 28 | success: () => { 29 | // Ensure loaded 30 | const twttr = window[twScriptWindowFieldName]; 31 | if (!twttr || !twttr.widgets) { 32 | rejectWithError(); 33 | } 34 | resolve(twttr.widgets); 35 | }, 36 | error: rejectWithError 37 | }); 38 | }); 39 | } 40 | 41 | export function removeChildrenWithAttribute(node, attribute) { 42 | if (node) { 43 | node.querySelectorAll("*").forEach(child => { 44 | if (child.hasAttribute(attribute)) { 45 | child.remove(); 46 | } 47 | }); 48 | } 49 | } 50 | 51 | function is(x, y) { 52 | if (x === y) { 53 | return x !== 0 || y !== 0 || 1 / x === 1 / y; 54 | } else { 55 | return x !== x && y !== y; 56 | } 57 | } 58 | function isShallowEqual(objA, objB) { 59 | if (is(objA, objB)) { 60 | return true; 61 | } 62 | 63 | if ( 64 | typeof objA !== "object" || 65 | objA === null || 66 | typeof objB !== "object" || 67 | objB === null 68 | ) { 69 | return false; 70 | } 71 | 72 | const keysA = Object.keys(objA); 73 | const keysB = Object.keys(objB); 74 | 75 | if (keysA.length !== keysB.length) { 76 | return false; 77 | } 78 | 79 | for (let i = 0; i < keysA.length; i++) { 80 | if ( 81 | !Object.prototype.hasOwnProperty.call(objB, keysA[i]) || 82 | !is(objA[keysA[i]], objB[keysA[i]]) 83 | ) { 84 | return false; 85 | } 86 | } 87 | 88 | return true; 89 | } 90 | 91 | export function useShallowCompareMemoize(value) { 92 | const ref = useRef(); 93 | if (!isShallowEqual(value, ref.current)) { 94 | ref.current = value; 95 | } 96 | return ref.current; 97 | } 98 | 99 | export function cloneShallow(value) { 100 | return typeof value === "object" ? Object.assign({}, value) : value; 101 | } 102 | -------------------------------------------------------------------------------- /stories/0-Follow.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Follow } from "../src"; 3 | 4 | export default { 5 | title: "Follow", 6 | component: Follow 7 | }; 8 | 9 | export const Basic = () => ; 10 | 11 | export const Large = () => ( 12 | 13 | ); 14 | 15 | export const Japanese = () => ( 16 | 17 | ); 18 | 19 | export const DoNotTrack = () => ( 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /stories/1-Hashtag.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Hashtag } from "../src"; 3 | 4 | export default { 5 | title: "Hashtag", 6 | component: Hashtag 7 | }; 8 | 9 | export const Basic = () => ; 10 | 11 | export const Large = () => ( 12 | 13 | ); 14 | 15 | export const Japanese = () => ( 16 | 17 | ); 18 | 19 | export const DoNotTrack = () => ( 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /stories/2-Mention.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Mention } from "../src"; 3 | 4 | export default { 5 | title: "Mention", 6 | component: Mention 7 | }; 8 | 9 | export const Basic = () => ; 10 | 11 | export const DefaultText = () => ( 12 | 13 | ); 14 | 15 | export const DefaultHashtags = () => ( 16 | 17 | ); 18 | 19 | export const Large = () => ( 20 | 21 | ); 22 | 23 | export const Japanese = () => ( 24 | 25 | ); 26 | 27 | export const DoNotTrack = () => ( 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /stories/3-Share.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Share } from "../src"; 3 | 4 | export default { 5 | title: "Share", 6 | component: Share 7 | }; 8 | 9 | export const Basic = () => ; 10 | 11 | export const DefaultText = () => ( 12 | 13 | ); 14 | 15 | export const DefaultHashtags = () => ( 16 | 20 | ); 21 | 22 | export const Large = () => ( 23 | 24 | ); 25 | 26 | export const Japanese = () => ( 27 | 28 | ); 29 | 30 | export const DoNotTrack = () => ( 31 | 32 | ); 33 | -------------------------------------------------------------------------------- /stories/4-Timeline.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Timeline } from "../src"; 3 | 4 | export default { 5 | title: "Timeline", 6 | component: Timeline 7 | }; 8 | 9 | export const ProfileBasic = () => ( 10 | 11 | ); 12 | 13 | export const ProfileLoadingError = () => ( 14 | 17 | "Could not load timeline! ...Your custom component here" 18 | } 19 | /> 20 | ); 21 | 22 | export const ProfileSmaller = () => ( 23 | 27 | ); 28 | 29 | export const ProfileDarkTheme = () => ( 30 | 34 | ); 35 | 36 | export const ProfileBorderColor = () => ( 37 | 41 | ); 42 | 43 | export const ProfileNoHeaderOrFooter = () => ( 44 | 48 | ); 49 | 50 | export const ProfileDoNotTrack = () => ( 51 | 55 | ); 56 | 57 | export const ProfileJapanese = () => ( 58 | 62 | ); 63 | 64 | export const ListBasic = () => ( 65 | 72 | ); 73 | 74 | export const UrlBasic = () => ( 75 | 78 | ); 79 | -------------------------------------------------------------------------------- /stories/5-Tweet.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Tweet } from "../src"; 3 | 4 | export default { 5 | title: "Tweet", 6 | component: Tweet 7 | }; 8 | 9 | export const Basic = () => ; 10 | 11 | export const TweetLoadingError = () => ( 12 | "Could not load tweet! ...Your custom component here"} 15 | /> 16 | ); 17 | 18 | export const Smaller = () => ( 19 | 20 | ); 21 | 22 | export const AlignRight = () => ( 23 | 27 | ); 28 | 29 | export const DarkTheme = () => ( 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /stories/6-Misc.stories.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Timeline } from "../src"; 3 | 4 | export default { 5 | title: "Misc" 6 | }; 7 | 8 | export const QuickUpdatesTest = () => { 9 | const [height, setHeight] = useState(400); 10 | 11 | return ( 12 | <> 13 |

14 | react-twitter-widgets protects against race conditions with many 15 | successive prop updates. Drag the slider to send many successive updates 16 | to timeline height. 17 |

18 |

19 | setHeight(e.target.value)} 25 | /> 26 |   27 | {height}px 28 |

29 | 33 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/index.test.js: -------------------------------------------------------------------------------- 1 | // import expect from "expect"; 2 | import React from "react"; 3 | import { render, unmountComponentAtNode } from "react-dom"; 4 | // import TestUtils from "react-dom/test-utils"; 5 | 6 | import { 7 | // Follow, 8 | // Hashtag, 9 | // Mention, 10 | // Share, 11 | // Timeline, 12 | Tweet, 13 | } from "src/"; 14 | 15 | describe("Tweet", () => { 16 | let node; 17 | 18 | beforeEach(() => { 19 | node = document.createElement("div"); 20 | document.body.appendChild(node); // required for twitter library 21 | }); 22 | 23 | afterEach(() => { 24 | unmountComponentAtNode(node); 25 | }); 26 | 27 | it("loads tweet widget", () => { 28 | render(, node, () => { 29 | // ... 30 | }); 31 | }); 32 | }); 33 | --------------------------------------------------------------------------------