Ответы пользователя по тегу React
  • Как реализовать эффект печатающегося текста?

    0xD34F
    @0xD34F Куратор тега React
    Состояние компонента должно содержать три значения - индекс печатаемой строки, количество отображаемых символов, направление печати (печатаем или стираем, т.е., в какую сторону надо изменять количество отображаемых символов, увеличивать или уменьшать). Дальше понятно что - setInterval или рекурсивный setTimeout, изменяем количество отображаемых символов, если дошли до конца строки - меняем направление, дошли до начала - меняем направление и индекс строки. Вот как-то так:

    const defaults = {
      index: 0,
      length: 0,
      step: 1,
    };
    
    function Typewriter({ strings, delay }) {
      const [ state, setState ] = useState(null);
    
      useEffect(() => {
        setState(() => ({...defaults}));
      }, [ strings ]);
    
      useEffect(() => {
        const timeoutId = setTimeout(setState, delay, ({...state}) => {
          state.length += state.step;
          if (state.length === strings[state.index].length) {
            state.step = -1;
          } else if (state.length === 0) {
            state.step = 1;
            state.index = (state.index + 1) % strings.length;
          }
          return state;
        });
    
        return () => clearTimeout(timeoutId);
      });
    
      return <div>{strings?.[state?.index]?.slice(0, state?.length)}</div>;
    }
    Ответ написан
    8 комментариев
  • Как сделать так, чтобы по потери фокуса input'ом измененный текст попадал в соответствующий li?

    0xD34F
    @0xD34F Куратор тега React
    Надо запоминать индекс кликнутого li.

    const defaultEdit = {
      index: -1,
      value: '',
    };
    
    function App() {
      const [ notes, setNotes ] = useState([...'12345']);
      const [ edit, setEdit ] = useState(defaultEdit);
    
      const onClick = ({ currentTarget: { dataset: { index } } }) =>
        setEdit({ index: +index, value: notes[index] });
    
      const onChange = ({ target: { value } }) =>
        setEdit(edit => ({ ...edit, value }));
    
      const onBlur = () => {
        setNotes(notes => notes.map((n, i) => i === edit.index ? edit.value : n));
        setEdit(defaultEdit);
      };
    
      return (
        <div>
          <ul>{notes.map((n, i) => (
            <li data-index={i} onClick={onClick}>
              {n}
            </li>))}
          </ul>
          <input
            value={edit.value}
            onChange={onChange}
            onBlur={onBlur}
          />
        </div>
      );
    }
    Ответ написан
  • Как подгрузить данные из объекта?

    0xD34F
    @0xD34F Куратор тега React
    Объект менять не нужно.

    Нужно.

    Так будет лучше, просто поверьте:

    const SERIES = [
      {
        comment: '1 сезон',
        folder: [
          { comment: '1 серия' },
          { comment: '2 серия' },
        ],
      },
      {
        comment: '2 сезон',
        folder: [
          { comment: '1 серия' },
          { comment: '2 серия' },
          { comment: '3 серия' },
        ],
      },
    ];
    
    const ItemsList = ({ header, items, active, onChange }) => (
      <div>
        <h3>{header}</h3>
        <ul className="items">
          {items.map(({ comment }, i) => (
            <li
              className={`item ${active === i ? 'active' : ''}`}
              onClick={() => onChange?.(i !== active ? i : -1)}
            >
              {comment}
            </li>
          ))}
        </ul>
      </div>
    );
    
    function App() {
      const [ iSeason, setActiveSeason ] = useState(-1);
      const [ iEpisode, setActiveEpisode ] = useState(-1);
      const season = SERIES[iSeason];
      const episode = season?.folder[iEpisode];
    
      useEffect(() => {
        setActiveEpisode(-1);
      }, [ iSeason ]);
    
      return (
        <div>
          <ItemsList
            header="сезоны"
            items={SERIES}
            active={iSeason}
            onChange={setActiveSeason}
          />
          {season && <ItemsList
            header="серии"
            active={iEpisode}
            onChange={setActiveEpisode}
            items={season.folder}
          />}
          {episode && <div>Выбрали: {season.comment}, {episode.comment}</div>}
        </div>
      );
    }
    Ответ написан
  • Почему при обновлении состояния не рендерится компонент?

    0xD34F
    @0xD34F Куратор тега React
    Не выдумывайте, всё рендерится.

    Почему видимых изменений нет? Потому что обновляете одно состояние, а рендер выполняете на основе другого, items в App и items в ElementsList - это разные массивы. Не надо никакого useState в ElementList, выполняйте рендер на основе prop'а; метод onDelete перенесите в App и тоже передавайте в ElementList через props.
    Ответ написан
    1 комментарий
  • Как изменить состояние дочернего компонента из родителя?

    0xD34F
    @0xD34F Куратор тега React
    Добавьте в дочерний компонент эффект, который будет зависеть от props'ов и обновлять состояние:

    useEffect(() => setValue(props.value), [ props.value ]);

    Это если прямо отвечать на спрошенное. Но действительно ли дочерний компонент должен хранить значение input'а у себя? Возможно, вы не конца понимаете, чего хотите, и вам надо узнать про подъём состояния.
    Ответ написан
    3 комментария
  • Как вывести вложенные данные в виде таблицы с объединенными ячейками?

    0xD34F
    @0xD34F Куратор тега React
    const rowspan = shards => shards.reduce((acc, n) => acc + n.jobs.length, 0);

    <table>
      <thead>
        <tr>{HEADERS.map(n => <th>{n}</th>)}</tr>
      </thead>
      <tbody>{
        DATA.flatMap(({ cloud, shards }) =>
        shards.flatMap((shard, iShard) =>
        shard.jobs.map((job, iJob) =>
          <tr>
            {!iShard && !iJob && <td rowSpan={rowspan(shards)}>{cloud}</td>}
            {!iJob && <td rowSpan={shard.jobs.length}>{shard.repository}</td>}
            {!iJob && <td rowSpan={shard.jobs.length}>{shard.repository_size_gb}</td>}
            <td>{job.job}</td>
            <td>{job.job_description}</td>
            <td>{job.backup_name}</td>
            <td>{job.backup_size_gb}</td>
          </tr>
        )))}
      </tbody>
    </table>

    https://jsfiddle.net/0ycjmnf3/
    Ответ написан
    Комментировать
  • Как в ивентах React отслеживать погружение - capture?

    0xD34F
    @0xD34F Куратор тега React
    Документация подсказывает, что есть такая штука, как

    onClickCapture: A version of onClick that fires in the capture phase.
    Ответ написан
    Комментировать
  • Как установить дефолтные точки на гугл карте api?

    0xD34F
    @0xD34F Куратор тега React
    fitBounds:

    // это массив ваших "точек"
    const locations = [
      { lat: ..., lng: ... },
      { lat: ..., lng: ... },
      ...
    ];

    const onLoad = map => {
      const bounds = new window.google.maps.LatLngBounds();
      locations.forEach(n => bounds.extend(n));
      map.fitBounds(bounds);
    };

    <GoogleMap
      onLoad={onLoad}
      ...
    >
      {locations.map(n => <Marker position={n} />)}
    </GoogleMap>
    Ответ написан
    Комментировать
  • Как реализовать react-easy-sort?

    0xD34F
    @0xD34F Куратор тега React
    const [ items, setItems ] = useState([]);
    const [ src, setSrc ] = useState('');
    
    const onChange = e => {
      setSrc(e.target.value);
    };
    
    const onClick = () => {
      setSrc('');
      setItems([
        ...items,
        {
          id: 1 + Math.max(0, ...items.map(n => n.id)),
          src,
        },
      ]);
    };
    
    const onSortEnd = (iOld, iNew) => {
      setItems(([...items]) => (
        items.splice(iNew, 0, items.splice(iOld, 1)[0]),
        items
      ));
    };

    <div>
      <input value={src} onChange={onChange} />
      <button onClick={onClick}>add</button>
    </div>
    <SortableList onSortEnd={onSortEnd}>
      {items.map(n => (
        <SortableItem key={n.id}>
          <img src={n.src} />
        </SortableItem>
      ))}
    </SortableList>
    Ответ написан
  • Как переключаться между компонентами?

    0xD34F
    @0xD34F Куратор тега React
    const languages = [
      { name:  'Ru', Component: AppRu  },
      { name: 'Eng', Component: AppEng },
      ...
    ];

    const [ language, setLanguage ] = useState(0);
    const { name, Component } = languages[language];
    const onClick = () => setLanguage((language + 1) % languages.length);

    <button onClick={onClick}>{name}</button>
    <Component />
    Ответ написан
    Комментировать
  • Почему не работают JSS стили Loader?

    0xD34F
    @0xD34F Куратор тега React
    Как может работать то, что вы не используете?

    <div className={classes.bar1}></div>

    Класс bar1 есть, это хорошо. А почему нет bar? Плохо.

    UPD. Ну и в целом многословно как-то. Зачем копипастить по двенадцать раз одно и то же? В createUseStyles заменяем все barN на

    ...Object.fromEntries(Array.from({ length: 12 }, (_, i) => [
      `bar${i + 1}`,
      {
        transform: `rotate(${i * 30}deg) translate(0, -130%)`,
        animationDelay: `${i * 0.1 - 1.2}s`,
      },
    ])),

    А в компоненте Loader пусть будет так:

    <div className={`${classes.loader} js--loader`}>
      {Array.from(
        { length: 12 },
        (_, i) => <div className={`${classes.bar} ${classes[`bar${i + 1}`]}`}></div>
      )}
    </div>
    Ответ написан
    Комментировать
  • Как избавиться от бага - всплывающая подсказка не убирается по первому клику?

    0xD34F
    @0xD34F Куратор тега React
    Возникает проблема, что нужно нажать 2 раза на подсказку, чтобы она исчезла.

    Не она, а они. Выбираете подсказку - значение меняется. Поменялось значение - запускается эффект, в котором заново вычисляется массив подсказок, и они отображаются.

    Может, вместо эффекта вам стоит вычислять массив подсказок в обработчике onChange:

    const onChange = ({ target: { value } }) => {
      setVal(value);
      setPrintVariables(value.length > 1
        ? arrayVariables.filter(n => n.includes(value))
        : []
      );
    };

    <input value={val} onChange={onChange} />

    ??
    Ответ написан
    1 комментарий
  • Почему не применяются JSS стили к компонентам и элементам?

    0xD34F
    @0xD34F Куратор тега React
    Что, в одно ухо влетело, из другого вылетело? - один из косяков переехал сюда из предыдущего вопроса, вместо . здесь должны быть $:

    '&:checked + .todo-item__span::before'

    '&:checked + .todo-item__span::after'

    Одной пустой строки недостаточно:

    'content': '',

    'content': '""',
    Ответ написан
  • Как передать условие классов в компонент с помощью JSS?

    0xD34F
    @0xD34F Куратор тега React
    'todo-item--checked': {
        'border': '2px solid #2196f3',
        ' box-shadow': '0 0 10px 3px #2196f3',
    },

    'todo-item--checked .todo-item__description': {
        'color': '#6495EDFF',
    },

    'todo-item--checked': {
      'border': '2px solid #2196f3',
      'box-shadow': '0 0 10px 3px #2196f3',
      '& $todo-item__description': {
        'color': '#6495EDFF',
      },
    },


    <div className={`
        ${classes['todo-item__description']}
        ${classes['todo-item--checked .todo-item__description']}
    `}>

    <div className={classes['todo-item__description']}>
    Ответ написан
    Комментировать
  • Почему обработчик реагирует на событие, случившееся до его установки?

    0xD34F
    @0xD34F Куратор тега React
    Я так понимаю, вот она, причина, подъехала с версией 18.0.0:

    Consistent useEffect timing: React now always synchronously flushes effect functions if the update was triggered during a discrete user input event such as a click or a keydown event.

    Т.е., жмёте на кнопку, открывается окно, запускается эффект, устанавливается обработчик клика на document, дальше событие клика по кнопке продолжило всплывать и попало в только что установленный обработчик.

    При добавлении задержки, данная проблема уходит

    Можете ещё попробовать ловить событие в фазе погружения:

    document.addEventListener('click', handleOutsideClick, true);
    return () => document.removeEventListener('click', handleOutsideClick, true);
    Ответ написан
    Комментировать
  • Как отображать один из нескольких компонентов, по выбору пользователя?

    0xD34F
    @0xD34F Куратор тега React
    const components = [
      [   'Day', Components1 ],
      [  'Week', Components2 ],
      [ 'Month', Components3 ],
      [  'Year', Components4 ],
    ];

    const [ active, setActive ] = useState(-1);
    const Component = components[active]?.[1];
    const onClick = e => setActive(+e.target.dataset.index);

    {components.map((n, i) => <button data-index={i} onClick={onClick}>{n[0]}</button>)}
    {Component && <Component />}
    Ответ написан
    3 комментария
  • Почему возникает ошибка 'Warning: Each child in a list should have a unique "key" prop'?

    0xD34F
    @0xD34F Куратор тега React
    ключ уже есть

    А потом раз - и ключа нет, undefined - это не ключ. Откуда берётся undefined? Из-за отсутствия свойства id, значение которого должно быть ключом. Как оно пропадает? Да вот так:

    return {
      ...emojis, count: emoji.count + 1
    }

    С какой целью вы делаете свойствами элемента массива все элементы самого массива?

    Да понятно, с какой.

    Ни с какой. Исправляйте опечатку - вместо ...emojis должно быть ...emoji.
    Ответ написан
    Комментировать
  • Как в react-yandex-map получить координаты левого нижнего угла и правого верхнего?

    0xD34F
    @0xD34F Куратор тега Яндекс.Карты
    Здесь, в разделе "props", вы можете найти способ получения экземпляра карты.

    А здесь, в разделе "методы", вы можете узнать, как, имея на руках экземпляр карты, получить нужные вам координаты.
    Ответ написан
    Комментировать
  • Как вывести смайл в компонент с наибольшим количеством кликов?

    0xD34F
    @0xD34F Куратор тега React
    Передавайте в Winner не количество голосов, а элемент smiles целиком. Да, Math.max получить его не поможет, придётся самостоятельно перебирать массив в поисках максимума.

    const Winner = ({ winner }) => winner
      ? <div>{winner.text}: {winner.value}</div>
      : null;

    class App extends React.Component {
      state = {
        winner: null,
        smiles: [ 128540, 128520, 128640, 128526, 128566 ].map((n, i) => ({
          id: i + 1,
          text: String.fromCodePoint(n),
          value: 0,
        })),
      }
    
      vote(index) {
        this.setState(({ smiles }) => ({
          winner: null,
          smiles: smiles.map((n, i) => i === index
            ? { ...n, value: n.value + 1 }
            : n
          ),
        }));
      }
    
      showWinner = () => {
        this.setState(({ smiles }) => ({
          winner: smiles.reduce((max, n) => max.value > n.value ? max : n),
        }));
      }
    
      render() {
        const { smiles, winner } = this.state;
    
        return (
          <div>
            <ul>
              {smiles.map((n, i) => (
                <li key={n.id}>
                  <button onClick={() => this.vote(i)}>{n.text}: {n.value}</button>
                </li>
              ))}
            </ul>
            <button onClick={this.showWinner}>show winner</button>
            <Winner winner={winner} />
          </div>
        );
      }
    }
    Ответ написан
    Комментировать