8,037 Matching Annotations
  1. Dec 2021
    1. Any "output selector" that just returns its inputs is incorrect! The output selector should always have the transformation logic.Similarly, a memoized selector should never use state => state as an input! That will force the selector to always recalculate.
    2. A somewhat common mistake is to write an "input selector" that extracts a value or does some derivation, and an "output selector" that just returns its result:
      // ❌ BROKEN: this will not memoize correctly, and does nothing useful!
      const brokenSelector = createSelector(
        state => state.todos,
        todos => todos
      )
      
    3. When you call the selector, Reselect will run your input selectors with all of the arguments you gave, and looks at the returned values. If any of the results are === different than before, it will re-run the output selector, and pass in those results as the arguments. If all of the results are the same as the last time, it will skip re-running the output selector, and just return the cached final result from before.

      This means that "input selectors" should usually just extract and return values, and the "output selector" should do the transformation work.

    4. createSelector can accept multiple input selectors, which can be provided as separate arguments or as an array. The results from all the input selectors are provided as separate arguments to the output selector:
      const selectA = state => state.a
      const selectB = state => state.b
      const selectC = state => state.c
      
      const selectABC = createSelector([selectA, selectB, selectC], (a, b, c) => {
        // do something with a, b, and c, and return a result
        return a + b + c
      })
      
      // Call the selector function and get a result
      const abc = selectABC(state)
      
      // could also be written as separate arguments, and works exactly the same
      const selectABC2 = createSelector(selectA, selectB, selectC, (a, b, c) => {
        // do something with a, b, and c, and return a result
        return a + b + c
      })
      
    5. Memoization is a form of caching. It involves tracking inputs to a function, and storing the inputs and the results for later reference. If a function is called with the same inputs as before, the function can skip doing the actual work, and return the same result it generated the last time it received those input values. This optimizes performance by only doing work if inputs have changed, and consistently returning the same result references if the inputs are the same.

    6. As an example, this component is written badly, because its useSelector call always returns a new array reference. That means the component will re-render after every dispatched action, even if the input state.todos slice hasn't changed:
      function TodoList() {
        // ❌ WARNING: this _always_ returns a new reference, so it will _always_ re-render!
        const completedTodos = useSelector(state =>
          state.todos.map(todo => todo.completed)
        )
      }
      
    7. Optimizing Selectors with Memoization​

      • Selectors used with useSelector or mapState will be re-run after every dispatched action, regardless of what section of the Redux root state was actually updated. Re-running expensive calculations when the input state sections didn't change is a waste of CPU time, and it's very likely that the inputs won't have changed most of the time anyway.
      • useSelector and mapState rely on === reference equality checks of the return values to determine if the component needs to re-render. If a selector always returns new references, it will force the component to re-render even if the derived data is effectively the same as last time. This is especially common with array operations like map() and filter(), which return new array references.
    1. For example, passing this selector to useSelector will cause the component to always re-render, because array.map() always returns a new array reference:
      // Bad: always returning a new reference
      const selectTodoDescriptions = state => {
        // This creates a new array reference!
        return state.todos.map(todo => todo.text)
      }
      
    2. However, there's a very important thing to remember here:

      useSelector compares its results using strict === reference comparisons, so the component will re-render any time the selector result is a new reference! This means that if you create a new reference in your selector and return it, your component could re-render every time an action has been dispatched, even if the data really isn't different.

    3. Fortunately, useSelector automatically subscribes to the Redux store for us! That way, any time an action is dispatched, it will call its selector function again right away. If the value returned by the selector changes from the last time it ran, useSelector will force our component to re-render with the new data. All we have to do is call useSelector() once in our component, and it does the rest of the work for us.
    1. I noticed that a lot of people are confused with useCallback. useCallback is pretty much the same as useMemo, but for functions. In fact, useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). useCallback is supposed to speed up the app when using a lof of callback functions by not redeclaring them unless the dependencies change. Here is our earlier example which uses useCallback:
      import React, {useState, useMemo, useCallback} from 'react';
      
      function App() {
        const [length, set_length] = useState(3);
        const [name, set_name] = useState('John Doe');
      
        const on_name_changed = useCallback((e) => set_name(e.target.value), []);
        const on_length_changed = useCallback((e) => set_length(Number(e.target.value)), []);
      
        return (
          <>
            <input value={name} onChange={on_name_changed} />
            <NameDisplay name={name}/>
            <hr />
            <input value={length} onChange={on_length_changed} />
            <FibDisplay length={length} />
          </>
        );
      }
      
      function FibDisplay({length}) {
        const numbers = useMemo(() => {
          console.log('Calculating numbers...');
          const result = [1, 1];
          for (let i = 2; i < length; i++) {
            result[i] = result[i - 1] + result[i - 2];
          }
          return result;
        }, [length]);
      
        return <p>{length} numbers of the fibonacci sequence: {numbers.join(', ')}</p>;
      }
      
      const NameDisplay = React.memo(function ({name}) {
        console.log('Rerendering name...');
        return <p>Your name is {name}</p>;
      });
      
      export default App;
      
    2. Considering React.memo, all these concerns apply as well. But there is one more: React.memo should only be applied to pure components.

      Another issue has to do with Redux/Context and hooks. Before hooks, the Redux selectors passed store values via props and React.memo would capture them. However, if you are using useSelector/useContext, React.memo will not rerender your component when those change. Because of these complexities, I would advise against React.memo, as useMemo should be sufficient in most cases.

    3. Firstly, notice the use of useMemo in FibDisplay. We wrapped the expensive computation in a function that will run only when the length changes. The component will still rerender, but the expensive computation will not run unless required. Secondly, notice that the NameDisplay component is wrapped with React.memo. React.memo is a way to memorize the whole component. It will rerender only when props change, thus solving our problem completely.
      // Memoized
      import React, {useState, useMemo} from 'react';
      
      function App() {
        const [length, set_length] = useState(3);
        const [name, set_name] = useState('John Doe');
      
        return (
          <>
            <input value={name} onChange={e => set_name(e.target.value)} />
            <NameDisplay name={name}/>
            <hr />
            <input value={length} onChange={e => set_length(Number(e.target.value))} />
            <FibDisplay length={length} />
          </>
        );
      }
      
      function FibDisplay({length}) {
        const numbers = useMemo(() => {
          console.log('Calculating numbers...');
          const result = [1, 1];
          for (let i = 2; i < length; i++) {
            result[i] = result[i - 1] + result[i - 2];
          }
          return result;
        }, [length]);
      
        return <p>{length} numbers of the fibonacci sequence: {numbers.join(', ')}</p>;
      }
      
      const NameDisplay = React.memo(function ({name}) {
        console.log('Rerendering name...');
        return <p>Your name is {name}</p>;
      });
      
      export default App;
      
    4. This small app will let you enter your name and a number. It will then greet you and display numbers from the Fibonacci sequence. If you run it, you will notice that both the NameDisplay component and the FibDisplay will rerender (and run the expensive computation) if we change the name or the number.
      import React, {useState} from 'react';
      
      function App() {
        const [length, set_length] = useState(3);
        const [name, set_name] = useState('John Doe');
      
        return (
          <>
            <input value={name} onChange={e => set_name(e.target.value)} />
            <NameDisplay name={name}/>
            <hr />
            <input value={length} onChange={e => set_length(Number(e.target.value))} />
            <FibDisplay length={length} />
          </>
        );
      }
      
      function FibDisplay({length}) {
        console.log('Calculating numbers & rerendering...');
        const numbers = [1, 1];
        for (let i = 2; i < length; i++) {
          numbers[i] = numbers[i - 1] + numbers[i - 2];
        }
      
        return <p>{length} numbers of the fibonacci sequence: {numbers.join(', ')}</p>;
      }
      
      function NameDisplay({name}) {
        console.log('Rerendering name...');
        return <p>Your name is {name}</p>;
      }
      
      export default App;
      
    1. Components using hooks can be freely wrapped in React.memo() to achieve memoization. React always re-renders the component if the state changes, even if the component is wrapped in React.memo().
    2. Even if provided with the same username value, MemoizedLogout renders every time because it receives new instances of onLogout callback. Memoization is broken. To fix it, onLogout prop must receive the same callback instance. Let's apply useCallback() to preserve the callback instance between renderings:
      const MemoizedLogout = React.memo(Logout);
      function MyApp({ store, cookies }) {
        const onLogout = useCallback(
          () => cookies.clear('session'), 
          [cookies]
        );
        return (
          <div className="main">
            <header>
              <MemoizedLogout
                username={store.username}
                onLogout={onLogout}
              />
            </header>
            {store.content}
          </div>
        );
      }
      
    3. A component that accepts a callback must be handled with care when applying memoization. The parent component could provide different instances of the callback function on every render:
      function MyApp({ store, cookies }) {
        return (
          <div className="main">
            <header>
              <MemoizedLogout
                username={store.username}
                onLogout={() => cookies.clear('session')}
              />
            </header>
            {store.content}
          </div>
        );
      }
      
    4. Every time a parent component defines a callback for its child, it creates new function instances. Let's see how this breaks memoization, and how to fix it. The following component Logout accepts a callback prop onLogout:
      function Logout({ username, onLogout }) {
        return (
          <div onClick={onLogout}>
            Logout {username}
          </div>
        );
      }
      const MemoizedLogout = React.memo(Logout);
      
    5. Let's use the memoized component MemoizedMovie inside MovieViewsRealtime to prevent useless re-renderings:
      function MovieViewsRealtime({ title, releaseDate, views }) {
        return (
          <div>
            <MemoizedMovie title={title} releaseDate={releaseDate} />
            Movie views: {views}
          </div>
        )
      }
      
    6. The best case of wrapping a component in React.memo() is when you expect the functional component to render often and usually with the same props. A common situation that makes a component render with the same props is being forced to render by a parent component.
      function MovieViewsRealtime({ title, releaseDate, views }) {
        return (
          <div>
            <Movie title={title} releaseDate={releaseDate} />
            Movie views: {views}
          </div>
        );
      }
      
    7. For example, let's manually calculate if Movie component props are equal:
      function moviePropsAreEqual(prevMovie, nextMovie) {
        return prevMovie.title === nextMovie.title
          && prevMovie.releaseDate === nextMovie.releaseDate;
      }
      const MemoizedMovie2 = React.memo(Movie, moviePropsAreEqual);
      
    8. By default React.memo() does a shallow comparison of props and objects of props. To customize the props comparison you can use the second argument to indicate an equality check function:
      React.memo(Component, [areEqual(prevProps, nextProps)]);
      
    9. export function Movie({ title, releaseDate }) { return ( <div> <div>Movie title: {title}</div> <div>Release date: {releaseDate}</div> </div> );}export const MemoizedMovie = React.memo(Movie);
      export function Movie({ title, releaseDate }) {
        return (
          <div>
            <div>Movie title: {title}</div>
            <div>Release date: {releaseDate}</div>
          </div>
        );
      }
      export const MemoizedMovie = React.memo(Movie);
      
    1. // To know the maximum numbers of thread that can be used
      // on user’s system, use
      
      window.navigator.hardwareConcurrency property.
      
    1. Using the threadpool To use this threadpool we now just have to do this:
         var pool = new Pool(6);
         pool.init();
         var workerTask = new WorkerTask('extractMainColor.js',callback,wp);
         pool.addWorkerTask(workerTask);
      
    2. The threadpool code To test multiple threads with web workers I wrote a simple (and very naive) threadpool / taskqueue. You can configure the maximum number of concurrent web workers when you create this pool, and any ‘task’ you submit will be executed using one of the available threads from the pool. Note that we aren’t really pooling threads, we’re just using this pool to control the number of concurrently executing web workers.
      function Pool(size) {
          var _this = this;
      
          // set some defaults
          this.taskQueue = [];
          this.workerQueue = [];
          this.poolSize = size;
      
          this.addWorkerTask = function(workerTask) {
              if (_this.workerQueue.length > 0) {
                  // get the worker from the front of the queue
                  var workerThread = _this.workerQueue.shift();
                  workerThread.run(workerTask);
              } else {
                  // no free workers,
                  _this.taskQueue.push(workerTask);
              }
          }
      
          this.init = function() {
              // create 'size' number of worker threads
              for (var i = 0 ; i < size ; i++) {
                  _this.workerQueue.push(new WorkerThread(_this));
              }
          }
      
          this.freeWorkerThread = function(workerThread) {
              if (_this.taskQueue.length > 0) {
                  // don't put back in queue, but execute next task
                  var workerTask = _this.taskQueue.shift();
                  workerThread.run(workerTask);
              } else {
                  _this.taskQueue.push(workerThread);
              }
          }
      }
      
      // runner work tasks in the pool
      function WorkerThread(parentPool) {
      
          var _this = this;
      
          this.parentPool = parentPool;
          this.workerTask = {};
      
          this.run = function(workerTask) {
              this.workerTask = workerTask;
              // create a new web worker
              if (this.workerTask.script!= null) {
                  var worker = new Worker(workerTask.script);
                  worker.addEventListener('message', dummyCallback, false);
                  worker.postMessage(workerTask.startMessage);
              }
          }
      
          // for now assume we only get a single callback from a worker
          // which also indicates the end of this worker.
          function dummyCallback(event) {
              // pass to original callback
              _this.workerTask.callback(event);
      
              // we should use a seperate thread to add the worker
              _this.parentPool.freeWorkerThread(_this);
          }
      
      }
      
      // task to run
      function WorkerTask(script, callback, msg) {
      
          this.script = script;
          this.callback = callback;
          this.startMessage = msg;
      };
      
    1. const currentPage = // something calculated from ScrollPosition
      
      const lastResult = usePageQuery(currentPage - 1, { skip: currentPage === 1 }) // don't fetch pages before 0
      const currentResult = usePageQuery(currentPage)
      const nextResult = usePageQuery(currentPage + 1)
      
      const combined = useMemo(() => {
        const arr = new Array(pageSize * (currentPage + 1))
        for (const data of [lastResult.data, currentResult.data, nextResult.data]) {
          if (data) {
            arr.splice(data.offset, data.items.length, ...data.items)
          }
        }
        return arr
      }, [pageSize, currentPage, lastResult.data, currentResult.data, nextResult.data])
      
      // work with `combined` here
      
    1. The correct way to handle TypeScript functional destructuring is to define an interface and reference the interface after the destructure. TypeScript is then able to understand that the function takes an object argument that is of the shape of the Person interface and destructuring occurs as you would expect it to in ES6.
      // a 'Person' object requires 'first' and 'last' 
      // properties whose values are strings
      interface Person {
        first: string;
        last: string;
      }
      
      // This defines that the first functional argument 
      // must be an Array of 'Person' objects, then destructs
      // the first person's first and last name properties
      const helloFirstPerson = ([{ first, last }]: Person[]) =>
        `Hello ${first} ${last}!`;
      
      const people = [
        { first: 'John', last: 'Doe' },
        { first: 'Jane', last: 'Smith' }
      ];
      
      // outputs "Hello John Doe!"
      helloFirstPerson(people);
      
      /* --------- Example of a Type Error ----------- */
      // This creates an array argument that will be invalid for
      // the helloFirstPerson() function, because there are no 'last'
      // props on the 'people' objects
      const badArgs = [{ first: 'John' }, { first: 'Jane' } ];
      
      // Type Error! 
      // Property 'last' is missing in type '{ first: string; }'
      helloFirstPerson(badArgs);
      
    1. Writers use References to indicate that a message has a parent. The last identifier in References identifies the parent. The first identifier in References identifies the first article in the same thread. There may be more identifiers in References, with grandparents preceding parents, etc. (The basic idea is that a writer should copy References from the parent and append the parent's Message-ID. However, if there are more than about ten identifiers listed, the writer should eliminate the second one.)
    1. In the case of buffering, you could perhaps accumulate incoming messages in an array and periodically flush it on each requestAnimationFrame, rendering that list into the DOM. Alternatively, you could buffer them for some duration, like every second. If you’re appending them into an existing table, you’ll probably also want to use some form of table virtualization because rendering 100k rows is also going to be a huge bottleneck — and a form of backpressure!
  2. Nov 2021
    1. import { createRequire } from "module"; const require = createRequire(import.meta.url); const data = require("./data.json");
  3. Oct 2021
    1. A combination of good cross-site scripting hygiene, a secure HTTP only cookie for authentication and a CSRF token is a good combination for building a secure ecosystem for your PWA and web API.
  4. Sep 2021
  5. Aug 2021
  6. Jul 2021